├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── compile.md ├── doc ├── dqy.1 ├── endpoint_cases.ods ├── help.adoc ├── supported_types.txt ├── trace.md └── usage_examples.txt ├── docker ├── Dockerfile ├── cleanup.sh ├── dockerfile.alpine ├── dockerfile.arm64v8-alpine └── dockerfile.ubuntu ├── release.md ├── rustfmt.toml ├── scripts ├── add_rr.py ├── add_rr.sh ├── add_utest.py ├── all_rr.sh ├── all_rrs.rb ├── compile_musl.sh ├── create_enum.py ├── extract_rr.py ├── rename_bin.ps1 └── rename_bin.sh ├── src ├── args.rs ├── certgen.rs ├── cli_options.rs ├── display.rs ├── dns │ ├── buffer.rs │ ├── date_time.rs │ ├── message.rs │ ├── mod.rs │ └── rfc │ │ ├── a.rs │ │ ├── aaaa.rs │ │ ├── afsdb.rs │ │ ├── algorithm.rs │ │ ├── apl.rs │ │ ├── caa.rs │ │ ├── cert.rs │ │ ├── char_string.rs │ │ ├── cname.rs │ │ ├── csync.rs │ │ ├── dhcid.rs │ │ ├── dname.rs │ │ ├── dnskey.rs │ │ ├── domain.rs │ │ ├── domain2.rs │ │ ├── domain_orig.rs │ │ ├── ds.rs │ │ ├── eui48.rs │ │ ├── eui64.rs │ │ ├── flags.rs │ │ ├── header.rs │ │ ├── hinfo.rs │ │ ├── hip.rs │ │ ├── ipseckey.rs │ │ ├── kx.rs │ │ ├── loc.rs │ │ ├── mod.rs │ │ ├── mx.rs │ │ ├── naptr.rs │ │ ├── ns.rs │ │ ├── nsec.rs │ │ ├── nsec3.rs │ │ ├── nsec3param.rs │ │ ├── opcode.rs │ │ ├── openpgpkey.rs │ │ ├── opt │ │ ├── client_subnet.rs │ │ ├── cookie.rs │ │ ├── dau_dhu_n3u.rs │ │ ├── extended.rs │ │ ├── llq.rs │ │ ├── mod.rs │ │ ├── nsid.rs │ │ ├── opt_rr.rs │ │ ├── padding.rs │ │ ├── report_chanel.rs │ │ └── zoneversion.rs │ │ ├── packet_type.rs │ │ ├── ptr.rs │ │ ├── qclass.rs │ │ ├── qtype.rs │ │ ├── query.rs │ │ ├── question.rs │ │ ├── rdata.rs │ │ ├── resource_record.rs │ │ ├── response.rs │ │ ├── response_code.rs │ │ ├── rp.rs │ │ ├── rrlist.rs │ │ ├── rrsig.rs │ │ ├── soa.rs │ │ ├── srv.rs │ │ ├── sshfp.rs │ │ ├── svcb.rs │ │ ├── tkey.rs │ │ ├── tlsa.rs │ │ ├── txt.rs │ │ ├── type_bitmaps.rs │ │ ├── uri.rs │ │ ├── wallet.rs │ │ └── zonemd.rs ├── error.rs ├── handlebars.rs ├── lib.rs ├── lua.rs ├── main.rs ├── protocol.rs ├── query_info.rs ├── show.rs ├── templating.rs ├── trace.rs └── transport │ ├── crypto.rs │ ├── endpoint.rs │ ├── https.rs │ ├── mod.rs │ ├── network.rs │ ├── quic.rs │ ├── root_servers.rs │ ├── target.rs │ ├── tcp.rs │ ├── tls.rs │ └── udp.rs └── tests ├── integration_test.rs ├── pcap ├── a.pcap ├── aaaa.pcap ├── afsdb.pcap ├── apl.pcap ├── caa.pcap ├── cap1.pcap ├── cap2.pcap ├── cap3.pcap ├── cap4.pcap ├── cdnskey.pcap ├── cds.pcap ├── cert.pcap ├── cname.pcap ├── create_sample_data.sh ├── csync.pcap ├── dhcid.pcap ├── dlv.pcap ├── dname.pcap ├── dnskey.pcap ├── ds.pcap ├── eui48.pcap ├── eui64.pcap ├── foo.pcapng ├── get_wire_data.sh ├── hinfo.pcap ├── hip.pcap ├── integration_tests.rs.old ├── ipseckey.pcap ├── kx.pcap ├── loc.pcap ├── mx.pcap ├── naptr.pcap ├── ns.pcap ├── nsec.pcap ├── nsec3.pcap ├── nsec3param.pcap ├── openpgpkey.pcap ├── ptr.pcap ├── rp.pcap ├── rrsig.pcap ├── smimea.pcap ├── soa.pcap ├── srv.pcap ├── sshfp.pcap ├── svcb.pcap ├── tkey.pcap ├── tlsa.pcap ├── txt.pcap ├── uri.pcap └── zonemd.pcap ├── resolv.conf ├── test_bind9.sh ├── tpl.hbs └── venom ├── bind9.yml ├── bind9 ├── Dockerfile ├── cert │ ├── cert.der │ ├── cert.pem │ └── key.pem ├── clean-up.sh ├── db.dqy-test.dns ├── named.conf.local ├── named.conf.options └── run.sh ├── dqy.yml ├── endpoint-ipv4.yml ├── endpoint-ipv6.yml ├── idna.yml ├── rr.yml └── test_all.sh /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-22.04, windows-latest, macos-latest, macos-13, ubuntu-22.04-arm] 18 | toolchain: 19 | - stable 20 | 21 | steps: 22 | 23 | - name: Checkout repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Rust 27 | uses: dtolnay/rust-toolchain@master 28 | with: 29 | toolchain: stable 30 | 31 | - name: Build on ${{ runner.os }}-${{ runner.arch }} 32 | run: | 33 | echo "TRIPLE=$(rustc -vV | sed -n 's|host: ||p')" >> $GITHUB_ENV 34 | echo ${{ env.TRIPLE }} 35 | cargo test --bin dqy 36 | cargo build --release 37 | ./scripts/compile_musl.sh 38 | shell: bash 39 | 40 | - name: Archive compilation target for ${{ matrix.os }}-${{ matrix.version }} 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: dqy-${{ env.TRIPLE }} 44 | if-no-files-found: ignore 45 | path: | 46 | target/release/dqy 47 | target/release/dqy.exe 48 | 49 | - name: Archive compilation target for Ubuntu musl x64 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: dqy-x86_64-unknown-linux-musl 53 | if-no-files-found: ignore 54 | path: | 55 | target/x86_64-unknown-linux-musl/release/dqy 56 | 57 | - name: Archive compilation target for Ubuntu musl aarch64 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: dqy-aarch64-unknown-linux-musl 61 | if-no-files-found: ignore 62 | path: | 63 | target/aarch64-unknown-linux-musl/release/dqy 64 | 65 | # build_x86_musl: 66 | # runs-on: ubuntu-latest 67 | # steps: 68 | 69 | # - name: Checkout repo 70 | # uses: actions/checkout@v4 71 | 72 | # - name: Building using Docker 73 | # run: | 74 | # cd docker 75 | # docker build -t dqy --build-arg IMAGE=alpine . 76 | # docker run --name dqy dqy 77 | # docker cp dqy:/dqy/target/release/dqy . 78 | 79 | # - name: Create artifact 80 | # uses: actions/upload-artifact@v4 81 | # with: 82 | # name: dqy-x86_64-unknown-linux-musl 83 | # if-no-files-found: ignore 84 | # path: | 85 | # docker/dqy 86 | 87 | # build_arm64_musl: 88 | # runs-on: ubuntu-latest 89 | # steps: 90 | 91 | # - name: Checkout repo 92 | # uses: actions/checkout@v4 93 | 94 | # - name: Install Qemu 95 | # uses: docker/setup-qemu-action@v3 96 | 97 | # - name: Building using Docker 98 | # run: | 99 | # cd docker 100 | # docker build -t dqy --platform "linux/arm64" --build-arg IMAGE="arm64v8/alpine" . 101 | # docker run --name dqy dqy 102 | # docker cp dqy:/dqy/target/release/dqy . 103 | 104 | # - name: Create artifact 105 | # uses: actions/upload-artifact@v4 106 | # with: 107 | # name: dqy-arm64-unknown-linux-musl 108 | # if-no-files-found: ignore 109 | # path: | 110 | # docker/dqy 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | docker/images/ 17 | 18 | **/*.log 19 | 20 | # venom log and result files 21 | tests/venom/log 22 | 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./Cargo.toml", 4 | ], 5 | "rust-analyzer.showUnlinkedFileNotification": false, 6 | "Lua.diagnostics.globals": [ 7 | "dns", 8 | "info" 9 | ], 10 | "sarif-viewer.connectToGithubCodeScanning": "off" 11 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dnslib" 3 | edition = "2021" 4 | version = "0.5.2" #:version 5 | authors = ["Alain Viguier "] 6 | description = """ 7 | dqy is a DNS query tool inspired by dig, drill and dog. 8 | """ 9 | documentation = "https://github.com/dandyvica/dqy" 10 | homepage = "https://github.com/dandyvica/dqy" 11 | repository = "https://github.com/dandyvica/dqy" 12 | keywords = ["dns"] 13 | categories = ["command-line-utilities"] 14 | license = "MIT" 15 | rust-version = "1.82.0" 16 | 17 | [lib] 18 | name = "dnslib" 19 | path = "src/lib.rs" 20 | 21 | [dependencies] 22 | base16 = "0.2.1" 23 | base64 = "0.21.5" 24 | byteorder = "1.5.0" 25 | bytes = "1.9.0" 26 | chrono = "0.4.38" 27 | clap = { version = "4.5.26", features = ["cargo"] } 28 | colored = "2.2.0" 29 | enum_from = { git = "https://github.com/dandyvica/enum_from.git" } 30 | handlebars = "6.3.0" 31 | http = "1.0.0" 32 | idna = "1.0.3" 33 | lazy_static = "1.4.0" 34 | log = "0.4.22" 35 | mlua = { version = "0.9.4", features = [ "lua54", "serialize" ], optional = true } 36 | quinn = "0.11.6" 37 | rand = "0.8.5" 38 | rcgen = "0.13.1" 39 | regex = "1.11.1" 40 | reqwest = { version = "0.12.12", default-features = false, features = ["rustls-tls-webpki-roots", "blocking", "http2"] } 41 | resolving = { git = "https://github.com/dandyvica/resolving" } 42 | rustc_version_runtime = "0.3.0" 43 | rustls = { version = "0.23.23", default-features = false, features = ["std", "tls12", "ring"] } 44 | rustls-pki-types = "1.10.1" 45 | serde = { version = "1.0.195", features = [ "derive" ] } 46 | serde_json = { version = "1.0.111", features = ["preserve_order"] } 47 | simplelog = "0.12.2" 48 | # tera = "1.20.0" 49 | thiserror = "1.0.65" 50 | tokio = { version = "1", features = ["full"] } 51 | tokio-macros = { version = "0.2.0-alpha.6" } 52 | type2network = { git = "https://github.com/dandyvica/type2network" } 53 | type2network_derive = { git = "https://github.com/dandyvica/type2network/" } 54 | unicode-width = "0.2.0" 55 | webpki-roots = "0.26.8" 56 | 57 | [dev-dependencies] 58 | pcap-file = "2.0.0" 59 | 60 | [profile.release] 61 | strip = "debuginfo" 62 | 63 | [lints.clippy] 64 | upper_case_acronyms = "allow" 65 | unnecessary_cast = "allow" 66 | 67 | [[bin]] 68 | name = "certgen" 69 | path = "src/certgen.rs" 70 | 71 | [[bin]] 72 | name = "dqy" 73 | path = "src/main.rs" 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Alain Viguier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /compile.md: -------------------------------------------------------------------------------- 1 | # Compiling `dqy` 2 | If you want to compile `dqy` on your own, following are the instructions. 3 | 4 | ## With Lua scripting 5 | 6 | * on Linux: make sure pkg-config is installed ```sudo apt-get install pkg-config``` and Lua dev libs too: ```sudo apt install liblua5.4-dev``` 7 | * OS/X: ```brew install pkg-config``` and ```brew install lua@5.4``` 8 | * on Windows (using ```PowerShell```) 9 | * download ```pkg-config```: https://download.gnome.org/binaries/win32/dependencies/ 10 | * download ```Lua5.4 libs```: https://luabinaries.sourceforge.net/ 11 | * set environment variables: 12 | * ```Set-Item -Path env:LUA_LIB_NAME -Value "lua54"``` 13 | * ```Set-Item -Path env:LUA_LIB -Value "mypath_where_lua54_lib_are"``` 14 | * then: ```cargo build --release --features mlua``` 15 | 16 | ## Without Lua scripting 17 | ```cargo build --release``` 18 | -------------------------------------------------------------------------------- /doc/dqy.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: dqy 3 | .\" Author: Alain Viguier 4 | .\" Generator: Asciidoctor 2.0.16 5 | .\" Date: 2024-01-04 6 | .\" Manual: DQY 7 | .\" Source: DQY 8 | .\" Language: English 9 | .\" 10 | .TH "DQY" "1" "2024-01-04" "DQY" "DQY" 11 | .ie \n(.g .ds Aq \(aq 12 | .el .ds Aq ' 13 | .ss \n[.ss] 0 14 | .nh 15 | .ad l 16 | .de URL 17 | \fI\\$2\fP <\\$1>\\$3 18 | .. 19 | .als MTO URL 20 | .if \n[.g] \{\ 21 | . mso www.tmac 22 | . am URL 23 | . ad l 24 | . . 25 | . am MTO 26 | . ad l 27 | . . 28 | . LINKSTYLE blue R < > 29 | .\} 30 | .SH "NAME" 31 | dqy \- a DNS query tool 32 | .SH "SYNOPSIS" 33 | .sp 34 | \fBeve\fP [\fIOPTION\fP]... \fIFILE\fP... 35 | .SH "OPTIONS" 36 | .sp 37 | \fITransport options:\fP 38 | .sp 39 | \fB+udp\fP 40 | .RS 4 41 | Uses UDP for sending DNS messages. This is used by default. Default port is 53. 42 | .RE 43 | .sp 44 | \fB+tcp\fP 45 | .RS 4 46 | Uses TCP for sending DNS messages. This is used by default for AXFR query and if truncation bit if set in response. Default port is 53. 47 | .RE 48 | .sp 49 | \fB+tls, +dot\fP 50 | .RS 4 51 | Uses \fIDNS over TLS\fP protocol for transmitting DNS messages. Default port is 853. 52 | .RE 53 | .sp 54 | \fB+https, +doh\fP 55 | .RS 4 56 | Uses \fIDNS over HTTPS\fP protocol for transmitting DNS messages. Default port is 443. 57 | .RE 58 | .sp 59 | \fB\-p=, \-\-port=\fP 60 | .RS 4 61 | Uses PORT# instead of the standard ports of the transport. 62 | .RE 63 | .sp 64 | \fB\-4\fP 65 | .RS 4 66 | Uses only IPV4 addresses. 67 | .RE 68 | .sp 69 | \fB\-6\fP 70 | .RS 4 71 | Uses only IPV6 addresses. 72 | .RE 73 | .sp 74 | EDNS options: 75 | .sp 76 | \fB+dau\fP 77 | .RS 4 78 | Set the DNSSEC Algorithm Understood (DAU) option. 79 | .RE 80 | .sp 81 | \fB+dhu\fP 82 | .RS 4 83 | Set DS Hash Understood (DHU) option. 84 | .RE 85 | .sp 86 | Verbose options: 87 | .sp 88 | \fB+v\fP 89 | .RS 4 90 | Prints out information messages on stderr. 91 | .RE 92 | .sp 93 | \fB+vv, +v2\fP 94 | .RS 4 95 | Prints out information and warning messages on stderr. 96 | .RE 97 | .sp 98 | \fB+vvv, +v3\fP 99 | .RS 4 100 | Prints out information, warning and error messages on stderr. 101 | .RE 102 | .sp 103 | \fB+vvvv, +v4\fP 104 | .RS 4 105 | Prints out information, warning and error messages on stderr. 106 | .RE 107 | .sp 108 | \fB+vvvvv, +v5\fP 109 | .RS 4 110 | Prints out all trace messages on stderr. 111 | .RE 112 | .SH "EXIT STATUS" 113 | .sp 114 | \fB0\fP 115 | .RS 4 116 | Success. 117 | Image is a picture of a life form. 118 | .RE 119 | .sp 120 | \fB1\fP 121 | .RS 4 122 | Failure. 123 | Image is not a picture of a life form. 124 | .RE 125 | .SH "EXAMPLES" 126 | .sp 127 | .RS 4 128 | .ie n \{\ 129 | \h'-04'\(bu\h'+03'\c 130 | .\} 131 | .el \{\ 132 | . sp -1 133 | . IP \(bu 2.3 134 | .\} 135 | query root servers: dqy 136 | .RE 137 | .sp 138 | .RS 4 139 | .ie n \{\ 140 | \h'-04'\(bu\h'+03'\c 141 | .\} 142 | .el \{\ 143 | . sp -1 144 | . IP \(bu 2.3 145 | .\} 146 | query A record using configured resolvers: dqy A www.google.com 147 | .RE 148 | .SH "RESOURCES" 149 | .sp 150 | \fBProject web site:\fP \c 151 | .URL "https://eve.example.org" "" "" 152 | .SH "COPYING" 153 | .sp 154 | Copyright \(co 2008 Alain Viguier. 155 | .br 156 | Free use of this software is granted under the terms of the MIT License. 157 | .SH "AUTHOR" 158 | .sp 159 | Alain Viguier -------------------------------------------------------------------------------- /doc/endpoint_cases.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/doc/endpoint_cases.ods -------------------------------------------------------------------------------- /doc/help.adoc: -------------------------------------------------------------------------------- 1 | = dqy(1) 2 | Alain Viguier 3 | v0.1.0 4 | :doctype: manpage 5 | :manmanual: DQY 6 | :mansource: DQY 7 | :man-linkstyle: pass:[blue R < >] 8 | 9 | == Name 10 | 11 | dqy - a DNS query tool 12 | 13 | == Synopsis 14 | 15 | *eve* [_OPTION_]... _FILE_... 16 | 17 | == Options 18 | 19 | // *-o, --out-file*=_OUT_FILE_:: 20 | // Write result to file _OUT_FILE_. 21 | 22 | // *-c, --capture*:: 23 | // Capture specimen if it's a picture of a life form. 24 | 25 | _Transport options:_ 26 | 27 | *+udp*:: 28 | Uses UDP for sending DNS messages. This is used by default. Default port is 53. 29 | 30 | *+tcp*:: 31 | Uses TCP for sending DNS messages. This is used by default for AXFR query and if truncation bit if set in response. Default port is 53. 32 | 33 | *+tls, +dot*:: 34 | Uses _DNS over TLS_ protocol for transmitting DNS messages. Default port is 853. 35 | 36 | *+https, +doh*:: 37 | Uses _DNS over HTTPS_ protocol for transmitting DNS messages. Default port is 443. 38 | 39 | *-p=, --port=*:: 40 | Uses PORT# instead of the standard ports of the transport. 41 | 42 | *-4*:: 43 | Uses only IPV4 addresses. 44 | 45 | *-6*:: 46 | Uses only IPV6 addresses. 47 | 48 | 49 | EDNS options: 50 | 51 | *+dau*:: 52 | Set the DNSSEC Algorithm Understood (DAU) option. 53 | 54 | *+dhu*:: 55 | Set DS Hash Understood (DHU) option. 56 | 57 | Verbose options: 58 | 59 | *+v*:: 60 | Prints out information messages on stderr. 61 | 62 | *+vv, +v2*:: 63 | Prints out information and warning messages on stderr. 64 | 65 | *+vvv, +v3*:: 66 | Prints out information, warning and error messages on stderr. 67 | 68 | *+vvvv, +v4*:: 69 | Prints out information, warning and error messages on stderr. 70 | 71 | *+vvvvv, +v5*:: 72 | Prints out all trace messages on stderr. 73 | 74 | == Exit status 75 | 76 | *0*:: 77 | Success. 78 | Image is a picture of a life form. 79 | 80 | *1*:: 81 | Failure. 82 | Image is not a picture of a life form. 83 | 84 | == Examples 85 | 86 | * query root servers: dqy 87 | * query A record using configured resolvers: dqy A www.google.com 88 | * query A record using a specific resolver: dqy A www.google.com @1.1.1.1 89 | 90 | 91 | 92 | == Resources 93 | 94 | *Project web site:* https://eve.example.org 95 | 96 | == Copying 97 | 98 | Copyright (C) 2008 {author}. + 99 | Free use of this software is granted under the terms of the MIT License. -------------------------------------------------------------------------------- /doc/supported_types.txt: -------------------------------------------------------------------------------- 1 | A 2 | AAAA 3 | AFSDB 4 | APL 5 | CAA 6 | CDNSKEY 7 | CDS 8 | CERT 9 | CNAME 10 | CSYNC 11 | DHCID 12 | DLV 13 | DNAME 14 | DNSKEY 15 | DS 16 | EUI48 17 | EUI64 18 | HINFO 19 | HIP 20 | HTTPS 21 | IPSECKEY 22 | KX 23 | LOC 24 | MX 25 | NAPTR 26 | NS 27 | NSEC 28 | NSEC3 29 | NSEC3PARAM 30 | OPENPGPKEY 31 | OPT 32 | PTR 33 | RP 34 | RRSIG 35 | SMIMEA 36 | SOA 37 | SRV 38 | SSHFP 39 | SVCB 40 | TLSA 41 | TXT 42 | URI 43 | ZONEMD 44 | WALLET -------------------------------------------------------------------------------- /doc/usage_examples.txt: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | 4 | - Simple query 5 | $ dqy AAAA www.google.com 6 | $ dqy NS hk. 7 | 8 | - Multiple query 9 | $ dqy A AAAA MX TXT www.example.com 10 | 11 | - Use a specific resolver 12 | $ dqy A www.google.co.uk @1.1.1.1 13 | 14 | - Use DoT for a resolver supporting DNS over TLS 15 | $ dqy A AAAA MX TXT NSEC3 www.example.com @1.1.1.1 --tls 16 | 17 | - Use DoH for a resolver supporting DNS over HTTPS 18 | $ dqy A www.google.com @https://cloudflare-dns.com/dns-query --doh 19 | 20 | - Use DoQ 21 | $ dqy A www.google.com @quic://dns.adguard.com 22 | 23 | - Don't use colors in output 24 | $ dqy A AAAA MX TXT www.example.com --no-colors 25 | 26 | - Don't ask for recursion 27 | $ dqy AAAA www.google.com --no-recurse 28 | 29 | - Use DNSSEC 30 | $ dqy NS . --dnssec 31 | 32 | - IDNA support 33 | $ dqy AAAA ουτοπία.δπθ.gr 34 | $ dqy 中国.asia 35 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # used to compile Linux x64 musl 2 | # build image with: docker build -t dqy-alpine -f dockerfile.alpine . 3 | # run a container from image: docker run -d -it --name dqy-alpine-ctr dqy-alpine /bin/sh 4 | # compile dqy: 5 | 6 | # build dqy inside container 7 | 8 | ARG IMAGE 9 | FROM $IMAGE 10 | 11 | RUN <compile.sh 20 | echo "git pull" >> compile.sh 21 | echo "source ~/.cargo/env" >> compile.sh 22 | echo "cargo build --release" >> compile.sh 23 | chmod +x compile.sh 24 | EOF 25 | 26 | CMD /compile.sh 27 | -------------------------------------------------------------------------------- /docker/cleanup.sh: -------------------------------------------------------------------------------- 1 | # delete all containers and images 2 | docker stop -f $(docker ps -qa) 3 | docker rm -v -f $(docker ps -qa) 4 | docker rmi $(docker images -qa) -------------------------------------------------------------------------------- /docker/dockerfile.alpine: -------------------------------------------------------------------------------- 1 | # used to compile Linux x64 musl 2 | # build image with: docker build -t dqy-alpine -f dockerfile.alpine . 3 | # run a container from image: docker run -d -it --name dqy-alpine-ctr dqy-alpine /bin/sh 4 | # compile dqy: 5 | 6 | # build dqy inside container 7 | 8 | ARG BASE_IMAGE 9 | FROM $BASE_IMAGE 10 | 11 | COPY compile.sh / 12 | 13 | RUN <compile.sh 22 | echo "git pull" >> compile.sh 23 | echo ". ~/.cargo/env" >> compile.sh 24 | echo "cargo build --release" >> compile.sh 25 | echo "strip /dqy/target/release/dqy" >> compile.sh 26 | chmod +x compile.sh 27 | EOF 28 | 29 | CMD /compile.sh 30 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | ## v0.2 2 | 3 | * IPV6 support 4 | * added https selection when https:// is found is server string 5 | * set timeout to 3s 6 | * added --resolve-file option 7 | * fixed display error on resource records -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 -------------------------------------------------------------------------------- /scripts/add_rr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | 4 | struct = """use std::fmt; 5 | 6 | // use log::trace; 7 | use type2network::FromNetworkOrder; 8 | use type2network_derive::FromNetwork; 9 | 10 | use super::type_bitmaps::TypeBitMaps; 11 | 12 | use crate::new_rd_length; 13 | 14 | // https://www.rfc-editor.org/rfc/rfc7477.html#section-2.1 15 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 16 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 17 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | // | SOA Serial | 19 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | // | Flags | Type Bit Map / 21 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | // / Type Bit Map (continued) / 23 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | #[derive(Debug, Default, FromNetwork)] 25 | pub struct {} {{ 26 | // transmistted through RR deserialization 27 | #[from_network(ignore)] 28 | pub(super) rd_length: u16, 29 | 30 | #[from_network(with_code( self.types = TypeBitMaps::new(self.rd_length - 6); ))] 31 | types: TypeBitMaps, 32 | }} 33 | 34 | // auto-implement new 35 | new_rd_length!({}); 36 | 37 | impl fmt::Display for {} {{ 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {{ 39 | write!(f, "{{}} {{}} {{}}", self.soa_serial, self.flags, self.types)?; 40 | 41 | Ok(()) 42 | }} 43 | }} 44 | 45 | #[cfg(test)] 46 | mod tests {{ 47 | use crate::{{ 48 | error::DNSResult, 49 | rfc::{{rdata::RData, response::Response}}, 50 | test_rdata, 51 | tests::{{get_pcap_buffer, read_pcap_sample}}, 52 | }}; 53 | 54 | use type2network::FromNetworkOrder; 55 | 56 | use super::{}; 57 | 58 | test_rdata!(rdata, 59 | "./tests/{}.pcap", 60 | RData::{}, 61 | (|x: &{}, _| {{ 62 | assert_eq!(&x.to_string(), ""); 63 | }}) 64 | ); 65 | }} 66 | """ 67 | 68 | type = sys.argv[1].upper() 69 | rr = sys.argv[1].lower() 70 | 71 | print(struct.format(type, type, type, type, rr, type, type)) 72 | -------------------------------------------------------------------------------- /scripts/add_rr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | # add a new RR in all source files 3 | if [ $# -eq 0 ]; then 4 | >&2 echo "No arguments provided" 5 | exit 1 6 | fi 7 | 8 | base=/data/projects/rust/dqy 9 | module=${1:l} 10 | 11 | # copy from another RR 12 | ./add_rr.py $module >$base/dns/src/rfc/$module.rs 13 | # cp $base/dns/src/rfc/dnskey.rs $base/dns/src/rfc/$module.rs 14 | 15 | # add ref to this new RR in the RData defition 16 | sed -i "/RData definition/a $1($1)," $base/dns/src/rfc/rdata.rs 17 | 18 | # add ref to this new RR in the RData Display trait 19 | line="RData::$1(a) => write!(f, \"{}\", a)," 20 | sed -i "/RData Display/a $line" $base/dns/src/rfc/rdata.rs 21 | 22 | # add ref to this new RR in the RData enum in resource record 23 | line="QType::$1 => self.r_data = get_rr!(buffer, $1, RData::$1)," 24 | sed -i "/RData enum/a $line" $base/dns/src/rfc/resource_record.rs 25 | 26 | # add module in mod.rs 27 | sed -i "/all RRs/a pub mod $module;" $base/dns/src/rfc/mod.rs 28 | 29 | # clean up 30 | cd $base/dns 31 | cargo fmt 32 | 33 | -------------------------------------------------------------------------------- /scripts/add_utest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | 4 | ut = """#[cfg(test)] 5 | mod tests {{ 6 | use crate::{{ 7 | error::DNSResult, 8 | rfc::{{rdata::RData, response::Response}}, 9 | test_rdata, 10 | tests::{{get_pcap_buffer, read_pcap_sample}}, 11 | }}; 12 | 13 | use type2network::FromNetworkOrder; 14 | 15 | use super::{0}; 16 | 17 | test_rdata!(rdata, 18 | "./tests/{1}.pcap", 19 | RData::{2}, 20 | (|x: &{3}, _| {{ 21 | assert_eq!(&x.to_string(), ""); 22 | }}) 23 | ); 24 | }} 25 | """ 26 | 27 | rr = sys.argv[1] 28 | type = rr.upper() 29 | 30 | print(ut.format(type, rr, type, type)) 31 | -------------------------------------------------------------------------------- /scripts/all_rr.sh: -------------------------------------------------------------------------------- 1 | set -x 2 | 3 | dqy @192.5.6.30 ANY com. --stats --bufsize=400 -vvvvv --no-opt 4 | dqy @a.gtld-servers.net ANY com. --bufsize=512 5 | dqy @f.root-servers.net hostname.bind chaos txt 6 | dqy @ns3.cloudflare.com any cloudflare.com 7 | dqy TYPE64 svcb.dns.netmeister.org. 8 | dqy a a.dns.netmeister.org. 9 | dqy a6 a6.dns.netmeister.org. 10 | dqy aaaa aaaa.dns.netmeister.org. 11 | dqy afsdb afsdb.dns.netmeister.org. 12 | dqy amtrelay amtrelay.dns.netmeister.org. 13 | dqy any any.dns.netmeister.org. 14 | dqy apl apl.dns.netmeister.org. 15 | dqy atma atma.dns.netmeister.org. 16 | dqy avc avc.dns.netmeister.org. 17 | dqy axfr @nsztm1.digi.ninja zonetransfer.me 18 | dqy caa caa.dns.netmeister.org. 19 | dqy cdnskey cdnskey.dns.netmeister.org. 20 | dqy cds cds.dns.netmeister.org. 21 | dqy cert cert.dns.netmeister.org. 22 | dqy cname cname.dns.netmeister.org. 23 | dqy csync csync.dns.netmeister.org. 24 | dqy dhcid dhcid.dns.netmeister.org. 25 | dqy dlv dlv.dns.netmeister.org. 26 | dqy dname dname.dns.netmeister.org. 27 | dqy dnskey dnskey.dns.netmeister.org. 28 | dqy doa doa.dns.netmeister.org. 29 | dqy ds ds.dns.netmeister.org. 30 | dqy eid eid.dns.netmeister.org. 31 | dqy eui48 eui48.dns.netmeister.org. 32 | dqy eui64 eui64.dns.netmeister.org. 33 | dqy gpos gpos.dns.netmeister.org. 34 | dqy hinfo hinfo.dns.netmeister.org. 35 | dqy hip hip.dns.netmeister.org. 36 | dqy ipseckey ipseckey.dns.netmeister.org. 37 | dqy isdn isdn.dns.netmeister.org. 38 | dqy key key.dns.netmeister.org. 39 | dqy kx kx.dns.netmeister.org. 40 | dqy l32 l32.dns.netmeister.org. 41 | dqy l64 l64.dns.netmeister.org. 42 | dqy loc loc.dns.netmeister.org. 43 | dqy lp lp.dns.netmeister.org. 44 | dqy mb mb.dns.netmeister.org. 45 | dqy mg mg.dns.netmeister.org. 46 | dqy minfo minfo.dns.netmeister.org. 47 | dqy mr mr.dns.netmeister.org. 48 | dqy mx mx.dns.netmeister.org. 49 | dqy naptr naptr.dns.netmeister.org. 50 | dqy nid nid.dns.netmeister.org. 51 | dqy nimloc nimloc.dns.netmeister.org. 52 | dqy ninfo ninfo.dns.netmeister.org. 53 | dqy ns ns.dns.netmeister.org. 54 | dqy nsap nsap.dns.netmeister.org. 55 | dqy nsap-ptr nsap-ptr.dns.netmeister.org. 56 | dqy nsec nsec.dns.netmeister.org. 57 | dqy nsec3 nsec3.dns.netmeister.org. 58 | dqy nsec3param nsec3param.dns.netmeister.org. 59 | dqy nxt nxt.dns.netmeister.org. 60 | dqy openpgpkey openpgpkey.dns.netmeister.org. 61 | dqy ptr ptr.dns.netmeister.org. 62 | dqy px px.dns.netmeister.org. 63 | dqy rp rp.dns.netmeister.org. 64 | dqy rrsig rrsig.dns.netmeister.org. 65 | dqy rt rt.dns.netmeister.org 66 | dqy sink sink.dns.netmeister.org 67 | dqy smimea smimea.dns.netmeister.org 68 | dqy soa dns.netmeister.org. 69 | dqy soa soa.dns.netmeister.org. 70 | dqy spf spf.dns.netmeister.org 71 | dqy srv srv.dns.netmeister.org 72 | dqy sshfp sshfp.dns.netmeister.org 73 | dqy ta ta.dns.netmeister.org. 74 | dqy talink talink.dns.netmeister.org. 75 | dqy tlsa tlsa.dns.netmeister.org. 76 | dqy txt a.dname.dns.netmeister.org. 77 | dqy txt txt.dns.netmeister.org. 78 | dqy txt txt.dns.netmeister.org. 79 | dqy uri uri.dns.netmeister.org. 80 | dqy wks wks.dns.netmeister.org. 81 | dqy x25 x25.dns.netmeister.org. 82 | dqy zonemd zonemd.dns.netmeister.org. 83 | 84 | -------------------------------------------------------------------------------- /scripts/all_rrs.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # call all RRs from the list and test for execution 3 | tests = <<~HEREDOC 4 | dqy @192.5.6.30 ANY com. --stats --bufsize=400 --no-opt 5 | dqy @a.gtld-servers.net ANY com. --bufsize=512 6 | dqy @f.root-servers.net hostname.bind chaos txt 7 | dqy @ns3.cloudflare.com any cloudflare.com 8 | dqy TYPE64 svcb.dns.netmeister.org. 9 | dqy a a.dns.netmeister.org. 10 | dqy a6 a6.dns.netmeister.org. 11 | dqy aaaa aaaa.dns.netmeister.org. 12 | dqy afsdb afsdb.dns.netmeister.org. 13 | dqy amtrelay amtrelay.dns.netmeister.org. 14 | dqy any any.dns.netmeister.org. 15 | dqy apl apl.dns.netmeister.org. 16 | dqy atma atma.dns.netmeister.org. 17 | dqy avc avc.dns.netmeister.org. 18 | dqy axfr @nsztm1.digi.ninja zonetransfer.me 19 | dqy caa caa.dns.netmeister.org. 20 | dqy cdnskey cdnskey.dns.netmeister.org. 21 | dqy cds cds.dns.netmeister.org. 22 | dqy cert cert.dns.netmeister.org. 23 | dqy cname cname.dns.netmeister.org. 24 | dqy csync csync.dns.netmeister.org. 25 | dqy dhcid dhcid.dns.netmeister.org. 26 | dqy dlv dlv.dns.netmeister.org. 27 | dqy dname dname.dns.netmeister.org. 28 | dqy dnskey dnskey.dns.netmeister.org. 29 | dqy doa doa.dns.netmeister.org. 30 | dqy ds ds.dns.netmeister.org. 31 | dqy eid eid.dns.netmeister.org. 32 | dqy eui48 eui48.dns.netmeister.org. 33 | dqy eui64 eui64.dns.netmeister.org. 34 | dqy gpos gpos.dns.netmeister.org. 35 | dqy hinfo hinfo.dns.netmeister.org. 36 | dqy hip hip.dns.netmeister.org. 37 | dqy ipseckey ipseckey.dns.netmeister.org. 38 | dqy isdn isdn.dns.netmeister.org. 39 | dqy key key.dns.netmeister.org. 40 | dqy kx kx.dns.netmeister.org. 41 | dqy l32 l32.dns.netmeister.org. 42 | dqy l64 l64.dns.netmeister.org. 43 | dqy loc loc.dns.netmeister.org. 44 | dqy lp lp.dns.netmeister.org. 45 | dqy mb mb.dns.netmeister.org. 46 | dqy mg mg.dns.netmeister.org. 47 | dqy minfo minfo.dns.netmeister.org. 48 | dqy mr mr.dns.netmeister.org. 49 | dqy mx mx.dns.netmeister.org. 50 | dqy naptr naptr.dns.netmeister.org. 51 | dqy nid nid.dns.netmeister.org. 52 | dqy nimloc nimloc.dns.netmeister.org. 53 | dqy ninfo ninfo.dns.netmeister.org. 54 | dqy ns ns.dns.netmeister.org. 55 | dqy nsap nsap.dns.netmeister.org. 56 | dqy nsap-ptr nsap-ptr.dns.netmeister.org. 57 | dqy nsec nsec.dns.netmeister.org. 58 | dqy nsec3 nsec3.dns.netmeister.org. 59 | dqy nsec3 nsec3.dns.netmeister.org. 60 | dqy nsec3param nsec3param.dns.netmeister.org. 61 | dqy nxt nxt.dns.netmeister.org. 62 | dqy openpgpkey openpgpkey.dns.netmeister.org. 63 | dqy ptr ptr.dns.netmeister.org. 64 | dqy px px.dns.netmeister.org. 65 | dqy rp rp.dns.netmeister.org. 66 | dqy rrsig rrsig.dns.netmeister.org. 67 | dqy rt rt.dns.netmeister.org 68 | dqy sink sink.dns.netmeister.org 69 | dqy smimea smimea.dns.netmeister.org 70 | dqy soa dns.netmeister.org. 71 | dqy soa soa.dns.netmeister.org. 72 | dqy spf spf.dns.netmeister.org 73 | dqy srv srv.dns.netmeister.org 74 | dqy sshfp sshfp.dns.netmeister.org 75 | dqy ta ta.dns.netmeister.org. 76 | dqy talink talink.dns.netmeister.org. 77 | dqy tlsa tlsa.dns.netmeister.org. 78 | dqy txt a.dname.dns.netmeister.org. 79 | dqy txt txt.dns.netmeister.org. 80 | dqy txt txt.dns.netmeister.org. 81 | dqy uri uri.dns.netmeister.org. 82 | dqy wks wks.dns.netmeister.org. 83 | dqy x25 x25.dns.netmeister.org. 84 | dqy zonemd zonemd.dns.netmeister.org. 85 | HEREDOC 86 | 87 | 88 | tests.split("\n").each do |x| 89 | # execute dqy 90 | cmd = x.strip 91 | puts "starting:#{cmd}" 92 | %x( #{cmd} ) 93 | 94 | puts $?.exitstatus 95 | end 96 | -------------------------------------------------------------------------------- /scripts/compile_musl.sh: -------------------------------------------------------------------------------- 1 | # download MUSL target and compile 2 | # only for Linux 3 | if [ $(uname) = "Linux" ]; then 4 | platform=$(uname -i) 5 | target="$platform-unknown-linux-musl" 6 | 7 | # install musl gcc 8 | sudo apt install musl-tools 9 | 10 | # add target 11 | rustup target add $target 12 | 13 | # compile 14 | cargo build --release --target=$target 15 | fi 16 | -------------------------------------------------------------------------------- /scripts/create_enum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # create enum from text taken from RFCs 3 | import sys 4 | 5 | name = sys.argv[1] 6 | 7 | text = """ 8 | 0 Reserved 9 | 1 PKIX X.509 as per PKIX 10 | 2 SPKI SPKI certificate 11 | 3 PGP OpenPGP packet 12 | 4 IPKIX The URL of an X.509 data object 13 | 5 ISPKI The URL of an SPKI certificate 14 | 6 IPGP The fingerprint and URL of an OpenPGP packet 15 | 7 ACPKIX Attribute Certificate 16 | 8 IACPKIX The URL of an Attribute Certificate 17 | 9-252 Available for IANA assignment 18 | 253 URI URI private 19 | 254 OID OID private 20 | 255 Reserved 21 | 256-65279 Available for IANA assignment 22 | 65280-65534 Experimental 23 | 65535 Reserved 24 | """ 25 | 26 | print(""" 27 | #[derive( 28 | Debug, 29 | Default, 30 | Copy, 31 | Clone, 32 | PartialEq, 33 | EnumFromStr, 34 | EnumTryFrom, 35 | EnumDisplay, 36 | ToNetwork, 37 | FromNetwork, 38 | )] 39 | #[repr(u16)] 40 | pub enum {} {{ 41 | """.format(name)) 42 | 43 | for line in text.split("\n"): 44 | tab = line.strip().split() 45 | 46 | if len(tab) >= 3: 47 | print(f"{tab[1]} = {tab[0]}, //{' '.join(tab[2:])}") 48 | 49 | print("}") 50 | -------------------------------------------------------------------------------- /scripts/extract_rr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | """ 4 | Simple extraction of all received RRs 5 | """ 6 | 7 | import sys 8 | import json 9 | 10 | # read JSON fro stdin 11 | json_str = sys.stdin.buffer.read() 12 | 13 | # convert to dict 14 | dns_data = json.loads(json_str) 15 | 16 | print(dns_data['response']['answer'][0]['type']) 17 | 18 | -------------------------------------------------------------------------------- /scripts/rename_bin.ps1: -------------------------------------------------------------------------------- 1 | # rename dqy Windows binary 2 | $triple = rustc -vV | rg -o 'x.*' 3 | Copy-Item .\target\release\dqy.exe .\target\release\dqy-${triple}.exe -------------------------------------------------------------------------------- /scripts/rename_bin.sh: -------------------------------------------------------------------------------- 1 | # rename dqy according to target triple 2 | triple=$(rustc -vV | sed -n 's|host: ||p') 3 | cp ./target/release/dqy ./target/release/dqy-$triple -------------------------------------------------------------------------------- /src/certgen.rs: -------------------------------------------------------------------------------- 1 | // used to create cert and private key for testing sqelf-sigend 2 | 3 | use rcgen::{generate_simple_self_signed, CertifiedKey}; 4 | use std::io::Write; 5 | use std::{error::Error, fs::File}; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Generate a certificate 9 | let subject_alt_names = vec!["127.0.0.1".to_string(), "localhost".to_string()]; 10 | let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap(); 11 | 12 | // save cert 13 | let mut cert_file = File::create("cert.pem")?; 14 | cert_file.write_all(cert.pem().as_bytes())?; 15 | cert_file.sync_all()?; 16 | 17 | // write private key 18 | let mut key_file = File::create("key.pem")?; 19 | key_file.write_all(key_pair.serialize_pem().as_bytes())?; 20 | key_file.sync_all()?; 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | // titles when displaying headers: build a map giving for each title its colored version 2 | use colored::*; 3 | use std::collections::HashMap; 4 | use std::sync::LazyLock; 5 | 6 | type ColoredTitles = HashMap; 7 | 8 | pub static TITLES: LazyLock = LazyLock::new(|| { 9 | const COLOR: Color = Color::BrightCyan; 10 | 11 | // local helper 12 | fn insert_title(h: &mut ColoredTitles, title: &str, color: Color) { 13 | h.insert(title.to_string(), title.color(color)); 14 | } 15 | 16 | // init new hmap 17 | let mut h = HashMap::new(); 18 | 19 | // add all titles 20 | insert_title(&mut h, "qname", COLOR); 21 | insert_title(&mut h, "qtype", COLOR); 22 | insert_title(&mut h, "qclass", COLOR); 23 | insert_title(&mut h, "name", COLOR); 24 | insert_title(&mut h, "type", COLOR); 25 | insert_title(&mut h, "payload", COLOR); 26 | insert_title(&mut h, "rcode", COLOR); 27 | insert_title(&mut h, "version", COLOR); 28 | insert_title(&mut h, "flags", COLOR); 29 | 30 | h 31 | }); 32 | 33 | pub fn header_section(text: &str, length: Option) -> ColoredString { 34 | let s = if let Some(l) = length { 35 | format!("{: type for the FromNetworkOrder trait 2 | use std::convert::AsRef; 3 | use std::fmt; 4 | use std::io::{Cursor, Read}; 5 | use std::ops::Deref; 6 | 7 | use base64::{engine::general_purpose, Engine as _}; 8 | 9 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 10 | 11 | #[derive(Default)] 12 | pub struct Buffer(Vec); 13 | 14 | impl Buffer { 15 | pub fn with_capacity>(len: T) -> Self { 16 | Self(vec![0; len.into()]) 17 | } 18 | 19 | // when printing out some RRs, it's easier to use this 20 | // pub fn to_string(&self) -> String { 21 | // String::from_utf8_lossy(&self.0).to_string() 22 | // } 23 | 24 | // some RRs need to convert the raw data into b16 or b64 hexa strings 25 | pub fn to_base64(&self) -> String { 26 | general_purpose::STANDARD.encode(self.as_ref()) 27 | } 28 | 29 | // some RRs need to convert the raw data into b16 or b64 hexa strings 30 | pub fn to_base16(&self) -> String { 31 | base16::encode_upper(&self.as_ref()) 32 | } 33 | 34 | // useful for JSON output 35 | pub fn to_hex(&self) -> String { 36 | format!("{:?}", self) 37 | } 38 | 39 | // fancier display 40 | pub fn display(&self) -> String { 41 | format!("0x{:?} \"{}\"", self, self) 42 | } 43 | } 44 | 45 | impl Deref for Buffer { 46 | type Target = [u8]; 47 | 48 | fn deref(&self) -> &Self::Target { 49 | &self.0 50 | } 51 | } 52 | 53 | impl fmt::Debug for Buffer { 54 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 55 | for c in self.iter() { 56 | write!(f, "{:X?}", c)?; 57 | } 58 | Ok(()) 59 | } 60 | } 61 | 62 | impl fmt::Display for Buffer { 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 64 | let s = String::from_utf8_lossy(self.0.as_ref()).to_string(); 65 | write!(f, "{}", s) 66 | } 67 | } 68 | 69 | impl fmt::UpperHex for Buffer { 70 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 71 | write!(f, "0x{:02X?}", self.0) 72 | } 73 | } 74 | 75 | // a more efficient serialize_to() for Vec 76 | impl ToNetworkOrder for Buffer { 77 | fn serialize_to(&self, buffer: &mut Vec) -> std::io::Result { 78 | let length = self.0.len(); 79 | buffer.extend(self); 80 | 81 | Ok(length) 82 | } 83 | } 84 | 85 | impl<'a> FromNetworkOrder<'a> for Buffer { 86 | fn deserialize_from(&mut self, buffer: &mut Cursor<&'a [u8]>) -> std::io::Result<()> { 87 | buffer.read_exact(self.0.as_mut_slice())?; 88 | Ok(()) 89 | } 90 | } 91 | 92 | // AsRef to benefit from already defined methods on Vec 93 | impl AsRef<[u8]> for Buffer { 94 | fn as_ref(&self) -> &[u8] { 95 | &self.0 96 | } 97 | } 98 | 99 | // IntoIterator to benefit from already defined iterator on Vec 100 | impl<'a> IntoIterator for &'a Buffer { 101 | type Item = &'a u8; 102 | type IntoIter = std::slice::Iter<'a, u8>; 103 | 104 | fn into_iter(self) -> Self::IntoIter { 105 | self.0.iter() 106 | } 107 | } 108 | 109 | // Custom serialization 110 | use serde::{Serialize, Serializer}; 111 | impl Serialize for Buffer { 112 | fn serialize(&self, serializer: S) -> std::result::Result 113 | where 114 | S: Serializer, 115 | { 116 | serializer.serialize_str(&self.to_string()) 117 | } 118 | } 119 | 120 | pub(crate) fn serialize_buffer(owner: &Buffer, serializer: S) -> Result 121 | where 122 | S: Serializer, 123 | { 124 | serializer.serialize_str(&owner.to_hex()) 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | use super::*; 130 | use crate::dns::tests::to_network_test; 131 | use std::io::Cursor; 132 | use type2network::FromNetworkOrder; 133 | 134 | #[test] 135 | fn network() { 136 | // to 137 | let mut buf = Buffer::with_capacity(3u16); 138 | buf.0 = vec![0, 1, 2]; 139 | to_network_test(&buf, 3, &[0, 1, 2]); 140 | 141 | // from 142 | let b = vec![0x12, 0x34, 0x56, 0x78]; 143 | let mut buffer = Cursor::new(b.as_slice()); 144 | let mut buf = Buffer::with_capacity(2u16); 145 | assert!(buf.deserialize_from(&mut buffer).is_ok()); 146 | assert_eq!(buf.as_ref(), &[0x12, 0x34]); 147 | assert!(buf.deserialize_from(&mut buffer).is_ok()); 148 | assert_eq!(buf.as_ref(), &[0x56, 0x78]); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/dns/date_time.rs: -------------------------------------------------------------------------------- 1 | // some RRs represent the number of seconds since EPOCH 2 | 3 | use std::fmt; 4 | 5 | use chrono::DateTime; 6 | 7 | use type2network::FromNetworkOrder; 8 | use type2network_derive::FromNetwork; 9 | 10 | #[derive(Debug, Default, PartialEq, FromNetwork)] 11 | pub struct DnsDateTime(u32); 12 | 13 | impl fmt::Display for DnsDateTime { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | let date_time = DateTime::from_timestamp(self.0 as i64, 0) 16 | .unwrap() 17 | .format("%Y%m%d%H%M%S"); 18 | write!(f, "{}", date_time)?; 19 | 20 | Ok(()) 21 | } 22 | } 23 | 24 | // Custom serialization 25 | use serde::{Serialize, Serializer}; 26 | impl Serialize for DnsDateTime { 27 | fn serialize(&self, serializer: S) -> std::result::Result 28 | where 29 | S: Serializer, 30 | { 31 | serializer.serialize_str(&self.to_string()) 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn datetime() { 41 | let dt = DnsDateTime(0); 42 | assert_eq!(dt.to_string(), "19700101000000"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/dns/message.rs: -------------------------------------------------------------------------------- 1 | //! A comination of a query and a response 2 | //! 3 | use std::{fmt, ops::Deref}; 4 | 5 | use super::rfc::{query::Query, response::Response, response_code::ResponseCode}; 6 | 7 | use log::{error, trace}; 8 | use serde::Serialize; 9 | 10 | // use crate::show::{header_section, DisplayOptions, QueryInfo, Show, ShowAll}; 11 | 12 | #[derive(Debug, Serialize)] 13 | pub struct Message { 14 | pub query: Query, 15 | pub response: Response, 16 | } 17 | 18 | impl Message { 19 | //─────────────────────────────────────────────────────────────────────────────────── 20 | // return a reference to the query part 21 | //─────────────────────────────────────────────────────────────────────────────────── 22 | pub fn query(&self) -> &Query { 23 | &self.query 24 | } 25 | 26 | //─────────────────────────────────────────────────────────────────────────────────── 27 | // return a reference to the response part 28 | //─────────────────────────────────────────────────────────────────────────────────── 29 | pub fn response(&self) -> &Response { 30 | &self.response 31 | } 32 | 33 | //─────────────────────────────────────────────────────────────────────────────────── 34 | // check if response corresponds to what the client sent 35 | //─────────────────────────────────────────────────────────────────────────────────── 36 | pub fn check(&self) -> crate::error::Result<()> { 37 | trace!("checking message validity"); 38 | 39 | if self.response.id() != self.query.header.id || self.query.question != self.response.question { 40 | error!( 41 | "query and response ID are not equal, discarding answer for type {:?}", 42 | self.query.question.qtype 43 | ); 44 | } 45 | 46 | // if self.response.rcode() != ResponseCode::NoError { 47 | // return Err(crate::error::Error::Internal(ProtocolError::ResponseError( 48 | // self.response.rcode(), 49 | // ))); 50 | // } 51 | 52 | // check return code 53 | if self.response.rcode() != ResponseCode::NoError 54 | || (self.response.rcode() == ResponseCode::NXDomain && self.response.ns_count() == 0) 55 | { 56 | eprintln!("response error:{}", self.response.rcode()); 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | // Return the max length of the response part 63 | #[inline] 64 | pub fn max_length(&self) -> usize { 65 | self.response.max_length() 66 | } 67 | } 68 | 69 | impl fmt::Display for Message { 70 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 71 | write!(f, "{}", self.query)?; 72 | write!(f, "{}", self.response)?; 73 | Ok(()) 74 | } 75 | } 76 | 77 | //─────────────────────────────────────────────────────────────────────────────────── 78 | // convenient struct for holding all messages 79 | //─────────────────────────────────────────────────────────────────────────────────── 80 | #[derive(Debug, Serialize)] 81 | pub struct MessageList(Vec); 82 | 83 | impl MessageList { 84 | pub fn new(list: Vec) -> Self { 85 | Self(list) 86 | } 87 | 88 | // Return the max length of all messages (all RRs of all messages) 89 | pub fn max_length(&self) -> Option { 90 | self.0.iter().map(|x| x.max_length()).max() 91 | } 92 | } 93 | 94 | impl Deref for MessageList { 95 | type Target = Vec; 96 | 97 | fn deref(&self) -> &Self::Target { 98 | &self.0 99 | } 100 | } 101 | 102 | impl fmt::Display for MessageList { 103 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 104 | for msg in self.iter() { 105 | write!(f, "{}", msg)?; 106 | } 107 | Ok(()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/dns/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub mod date_time; 3 | pub mod message; 4 | pub mod rfc; 5 | 6 | // Macro used to define getters 7 | #[macro_export] 8 | macro_rules! getter { 9 | ($struct:ident, $field:ident, $field_type:ty) => { 10 | impl $struct { 11 | pub fn $field(&self) -> $field_type { 12 | self.$field 13 | } 14 | } 15 | }; 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use std::fs::File; 21 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 22 | 23 | pub(crate) fn to_network_test(val: &T, size: usize, v: &[u8]) { 24 | let mut buffer: Vec = Vec::new(); 25 | assert_eq!(val.serialize_to(&mut buffer).unwrap(), size); 26 | assert_eq!(buffer, v); 27 | } 28 | 29 | pub(crate) fn from_network_test<'a, T>(def: Option, val: &T, buf: &'a Vec) 30 | where 31 | T: FromNetworkOrder<'a> + Default + std::fmt::Debug + std::cmp::PartialEq, 32 | { 33 | let mut buffer = std::io::Cursor::new(buf.as_slice()); 34 | let mut v: T = if def.is_none() { T::default() } else { def.unwrap() }; 35 | assert!(v.deserialize_from(&mut buffer).is_ok()); 36 | assert_eq!(&v, val); 37 | } 38 | 39 | // get packets from pcap file 40 | pub(crate) fn get_packets(pcap_file: &str, query: usize, response: usize) -> (Vec, Vec) { 41 | use pcap_file::pcap::PcapReader; 42 | 43 | let pcap = File::open(pcap_file).expect("Error opening pcap file"); 44 | let mut pcap_reader = PcapReader::new(pcap).unwrap(); 45 | 46 | let mut index = 0usize; 47 | 48 | let mut ret = (Vec::new(), Vec::new()); 49 | 50 | // iterate to find the query and response index 51 | while let Some(pkt) = pcap_reader.next_packet() { 52 | let pkt = pkt.unwrap(); 53 | 54 | if index == query { 55 | ret.0 = pkt.data.to_vec(); 56 | } else if index == response { 57 | ret.1 = pkt.data.to_vec(); 58 | } 59 | 60 | index += 1; 61 | } 62 | 63 | ret 64 | } 65 | 66 | // helper macro to create a function to test all RRs 67 | // allow to test several RRs in the answer 68 | #[macro_export] 69 | macro_rules! test_rdata { 70 | // pass function name, pcap file name, RData arm, closure containing unit tests 71 | // $fname: function name 72 | // $file: pcap file name 73 | // $tcp: true if TCP was used 74 | // $index: packet number in the pcap file 75 | // $arm: RData enum arm 76 | // $closure: code to test the function 77 | ($fname:ident, $file:literal, $tcp:literal, $index:literal, $arm:path, $closure:tt) => { 78 | #[test] 79 | fn $fname() -> crate::error::Result<()> { 80 | { 81 | // extract response packet 82 | let data = get_packets($file, 0, $index); 83 | 84 | // manage TCP length if any 85 | let mut resp_buffer = if $tcp { 86 | // DNS message starts at offset 0x44 when using TCP 87 | std::io::Cursor::new(&data.1[0x44..]) 88 | } else { 89 | // DNS message starts at offset 0x2A when using UDP 90 | std::io::Cursor::new(&data.1[0x2A..]) 91 | }; 92 | 93 | //println!("{:X?}", resp_buffer); 94 | 95 | let mut resp = Response::default(); 96 | let _ = resp.deserialize_from(&mut resp_buffer); 97 | 98 | let answer = resp.answer.unwrap(); 99 | 100 | for (i, a) in answer.iter().enumerate() { 101 | if let $arm(x) = &a.r_data { 102 | $closure(&x, i); 103 | //Ok(()) 104 | } else { 105 | panic!("RData not found in file {}", $file) 106 | } 107 | } 108 | 109 | Ok(()) 110 | } 111 | } 112 | }; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/dns/rfc/a.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, net::Ipv4Addr}; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | // A resource record 9 | #[allow(clippy::upper_case_acronyms)] 10 | #[derive(Debug, PartialEq, FromNetwork, Serialize)] 11 | pub struct A(pub Ipv4Addr); 12 | 13 | impl Default for A { 14 | fn default() -> Self { 15 | Self(Ipv4Addr::UNSPECIFIED) 16 | } 17 | } 18 | 19 | impl fmt::Display for A { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!(f, "{}", self.0) 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use std::net::Ipv4Addr; 28 | 29 | use crate::{ 30 | dns::rfc::{a::A, rdata::RData, response::Response}, 31 | dns::tests::get_packets, 32 | test_rdata, 33 | }; 34 | 35 | use type2network::FromNetworkOrder; 36 | 37 | test_rdata!( 38 | rdata, 39 | "./tests/pcap/a.pcap", 40 | false, 41 | 1, 42 | RData::A, 43 | (|x: &A, _| { 44 | let addr = Ipv4Addr::from(x.0).to_string(); 45 | assert_eq!(addr, "166.84.7.99"); 46 | }) 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/dns/rfc/aaaa.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, net::Ipv6Addr}; 2 | 3 | use serde::Serialize; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | // AAAA resource record 8 | #[allow(clippy::upper_case_acronyms)] 9 | #[derive(Debug, PartialEq, FromNetwork, Serialize)] 10 | pub struct AAAA(pub Ipv6Addr); 11 | 12 | impl Default for AAAA { 13 | fn default() -> Self { 14 | Self(Ipv6Addr::UNSPECIFIED) 15 | } 16 | } 17 | 18 | impl fmt::Display for AAAA { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | write!(f, "{}", self.0) 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use std::net::Ipv6Addr; 27 | 28 | use crate::{ 29 | dns::rfc::{aaaa::AAAA, rdata::RData, response::Response}, 30 | dns::tests::get_packets, 31 | test_rdata, 32 | }; 33 | 34 | use type2network::FromNetworkOrder; 35 | 36 | test_rdata!( 37 | rdata, 38 | "./tests/pcap/aaaa.pcap", 39 | false, 40 | 1, 41 | RData::AAAA, 42 | (|x: &AAAA, _| { 43 | let addr = Ipv6Addr::from(x.0).to_string(); 44 | assert_eq!(addr, "2001:470:30:84:e276:63ff:fe72:3900"); 45 | }) 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/dns/rfc/afsdb.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::Serialize; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use super::domain::DomainName; 8 | 9 | #[allow(clippy::upper_case_acronyms)] 10 | #[derive(Debug, Default, FromNetwork, Serialize)] 11 | pub struct AFSDB { 12 | subtype: u16, 13 | hostname: DomainName, 14 | } 15 | 16 | impl fmt::Display for AFSDB { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | write!(f, "{} {} ", self.subtype, self.hostname) 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use crate::{ 25 | dns::rfc::{afsdb::AFSDB, rdata::RData, response::Response}, 26 | dns::tests::get_packets, 27 | test_rdata, 28 | }; 29 | 30 | use type2network::FromNetworkOrder; 31 | 32 | test_rdata!( 33 | rdata, 34 | "./tests/pcap/afsdb.pcap", 35 | false, 36 | 1, 37 | RData::AFSDB, 38 | (|x: &AFSDB, _| { 39 | assert_eq!(x.subtype, 1u16); 40 | assert_eq!(x.hostname.to_string(), "panix.netmeister.org."); 41 | }) 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/dns/rfc/algorithm.rs: -------------------------------------------------------------------------------- 1 | use byteorder::ReadBytesExt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use enum_from::{EnumDisplay, EnumFromStr, EnumTryFrom}; 7 | 8 | #[derive(Debug, Default, Copy, Clone, PartialEq, EnumFromStr, EnumTryFrom, EnumDisplay, FromNetwork)] 9 | #[repr(u8)] 10 | #[from_network(TryFrom)] 11 | #[allow(non_camel_case_types)] 12 | pub enum Algorithm { 13 | #[default] 14 | DELETE = 0, 15 | RSAMD5 = 1, 16 | DH = 2, 17 | DSA = 3, 18 | RSASHA1 = 5, 19 | DSA_NSEC3_SHA1 = 6, 20 | RSASHA1_NSEC3_SHA1 = 7, 21 | RSASHA256 = 8, 22 | RSASHA512 = 10, 23 | ECC_GOST = 12, 24 | ECDSAP256SHA256 = 13, 25 | ECDSAP384SHA384 = 14, 26 | ED25519 = 15, 27 | ED448 = 16, 28 | INDIRECT = 252, 29 | PRIVATEDNS = 253, 30 | PRIVATEOID = 254, 31 | 32 | #[fallback] 33 | Reserved(u8), 34 | } 35 | 36 | // https://www.rfc-editor.org/rfc/rfc4034.html#appendix-A.1 37 | #[derive(Debug, Default, Copy, Clone, PartialEq, EnumFromStr, EnumTryFrom, EnumDisplay, FromNetwork)] 38 | #[repr(u8)] 39 | #[from_network(TryFrom)] 40 | #[allow(non_camel_case_types)] 41 | pub enum DNSSECAlgorithmTypes { 42 | #[default] 43 | DELETE = 0, 44 | RSAMD5 = 1, 45 | DH = 2, 46 | DSA = 3, 47 | RSASHA1 = 5, 48 | DSA_NSEC3_SHA1 = 6, 49 | RSASHA1_NSEC3_SHA1 = 7, 50 | RSASHA256 = 8, 51 | RSASHA512 = 10, 52 | ECC_GOST = 12, 53 | ECDSAP256SHA256 = 13, 54 | ECDSAP384SHA384 = 14, 55 | ED25519 = 15, 56 | ED448 = 16, 57 | INDIRECT = 252, 58 | PRIVATEDNS = 253, 59 | PRIVATEOID = 254, 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | use crate::dns::tests::from_network_test; 66 | 67 | #[test] 68 | fn network() { 69 | let a = Algorithm::ECDSAP256SHA256; 70 | from_network_test(None, &a, &vec![13]); 71 | 72 | let a = Algorithm::Reserved(255); 73 | from_network_test(None, &a, &vec![255]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/dns/rfc/caa.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use crate::{dns::buffer::Buffer, new_rd_length}; 7 | 8 | // https://datatracker.ietf.org/doc/html/rfc8659 9 | //------------------------------------------------------------------------------------- 10 | // CAA 11 | //------------------------------------------------------------------------------------- 12 | 13 | // +0-1-2-3-4-5-6-7-|0-1-2-3-4-5-6-7-| 14 | // | Flags | Tag Length = n | 15 | // +----------------+----------------+...+---------------+ 16 | // | Tag char 0 | Tag char 1 |...| Tag char n-1 | 17 | // +----------------+----------------+...+---------------+ 18 | // +----------------+----------------+.....+----------------+ 19 | // | Value byte 0 | Value byte 1 |.....| Value byte m-1 | 20 | // +----------------+----------------+.....+----------------+ 21 | #[allow(clippy::upper_case_acronyms)] 22 | #[derive(Debug, Default, FromNetwork, Serialize)] 23 | pub struct CAA { 24 | // transmistted through RR deserialization 25 | #[serde(skip_serializing)] 26 | #[from_network(ignore)] 27 | rd_length: u16, 28 | 29 | flags: u8, 30 | tag_length: u8, 31 | 32 | #[from_network(with_code( self.tag_key = Buffer::with_capacity(self.tag_length); ))] 33 | tag_key: Buffer, 34 | 35 | #[from_network(with_code( self.tag_value = Buffer::with_capacity(self.rd_length - self.tag_length as u16 - 2 ); ))] 36 | tag_value: Buffer, 37 | } 38 | 39 | // auto-implement new 40 | new_rd_length!(CAA); 41 | 42 | impl fmt::Display for CAA { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | write!(f, "{} {} \"{}\"", self.flags, self.tag_key, self.tag_value) 45 | } 46 | } 47 | 48 | use serde::Serialize; 49 | // impl Serialize for CAA { 50 | // fn serialize(&self, serializer: S) -> std::result::Result 51 | // where 52 | // S: Serializer, 53 | // { 54 | // let mut seq = serializer.serialize_map(Some(3))?; 55 | // seq.serialize_entry("flags", &self.flags)?; 56 | // seq.serialize_entry("tag_key", &self.tag_key.to_string())?; 57 | // seq.serialize_entry("tag_value", &self.tag_value.to_string())?; 58 | // seq.end() 59 | // } 60 | // } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use crate::{ 65 | dns::rfc::{rdata::RData, response::Response}, 66 | dns::tests::get_packets, 67 | test_rdata, 68 | }; 69 | 70 | use type2network::FromNetworkOrder; 71 | 72 | use super::CAA; 73 | 74 | test_rdata!( 75 | rdata, 76 | "./tests/pcap/caa.pcap", 77 | false, 78 | 1, 79 | RData::CAA, 80 | (|x: &CAA, i: usize| { 81 | match i { 82 | 0 => assert_eq!(x.to_string(), "0 issue \";\""), 83 | 1 => assert_eq!(x.to_string(), "0 issuewild \";\""), 84 | 2 => assert_eq!(x.to_string(), "0 iodef \"mailto:abuse@netmeister.org\""), 85 | _ => panic!("data not is the pcap file"), 86 | } 87 | }) 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /src/dns/rfc/char_string.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io::{Cursor, Seek, SeekFrom}; 3 | use std::ops::Deref; 4 | 5 | use type2network::FromNetworkOrder; 6 | 7 | use serde::{Serialize, Serializer}; 8 | 9 | use super::DataLength; 10 | 11 | // Character string as described in: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4 12 | #[derive(Debug, Default, PartialEq)] 13 | pub struct CharacterString { 14 | length: u8, 15 | data: Vec, 16 | } 17 | 18 | impl CharacterString { 19 | #[inline] 20 | pub fn len(&self) -> u8 { 21 | self.length 22 | } 23 | 24 | #[inline] 25 | pub fn is_empty(&self) -> bool { 26 | self.length == 0 27 | } 28 | } 29 | 30 | impl DataLength for CharacterString { 31 | fn size(&self) -> u16 { 32 | self.length as u16 + 1 33 | } 34 | } 35 | 36 | impl From<&str> for CharacterString { 37 | fn from(s: &str) -> Self { 38 | CharacterString { 39 | length: s.len() as u8, 40 | data: s.as_bytes().to_vec(), 41 | } 42 | } 43 | } 44 | 45 | impl fmt::Display for CharacterString { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | write!(f, "{}", String::from_utf8_lossy(&self.data)) 48 | } 49 | } 50 | 51 | impl Serialize for CharacterString { 52 | fn serialize(&self, serializer: S) -> std::result::Result 53 | where 54 | S: Serializer, 55 | { 56 | serializer.serialize_str(&self.to_string()) 57 | } 58 | } 59 | 60 | impl<'a> FromNetworkOrder<'a> for CharacterString { 61 | fn deserialize_from(&mut self, buffer: &mut Cursor<&'a [u8]>) -> std::io::Result<()> { 62 | // copy text length 63 | self.length.deserialize_from(buffer)?; 64 | let current_position = buffer.position() as usize; 65 | 66 | // slice to data 67 | let s = &buffer.get_ref()[current_position..current_position + self.length as usize]; 68 | self.data = s.to_vec(); 69 | 70 | // don't forget to move the position 71 | buffer.seek(SeekFrom::Current(self.length as i64))?; 72 | 73 | Ok(()) 74 | } 75 | } 76 | 77 | // in some contexts, we need to deal with a buffer containing continuous CharacterString 78 | // like TXT RR or SVCB alpn param 79 | #[derive(Debug, Default)] 80 | pub struct CSList(Vec); 81 | 82 | // IntoIterator to benefit from already defined iterator on Vec 83 | impl<'a> IntoIterator for &'a CSList { 84 | type Item = &'a CharacterString; 85 | type IntoIter = std::slice::Iter<'a, CharacterString>; 86 | 87 | fn into_iter(self) -> Self::IntoIter { 88 | self.0.iter() 89 | } 90 | } 91 | 92 | impl Deref for CSList { 93 | type Target = Vec; 94 | fn deref(&self) -> &Self::Target { 95 | &self.0 96 | } 97 | } 98 | 99 | impl From<&[u8]> for CSList { 100 | fn from(s: &[u8]) -> Self { 101 | let mut v = Vec::new(); 102 | let mut index = 0; 103 | 104 | while index < s.len() { 105 | let length = s[index] as usize; 106 | let cs = CharacterString { 107 | length: length as u8, 108 | data: s[index + 1..index + length + 1].to_vec(), 109 | }; 110 | v.push(cs); 111 | 112 | index += length + 1; 113 | } 114 | CSList(v) 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use super::*; 121 | use std::io::Cursor; 122 | 123 | #[test] 124 | fn from() { 125 | let cs = CharacterString::from("www"); 126 | assert_eq!(cs.length, 3u8); 127 | assert_eq!(cs.data, &[119, 119, 119]); 128 | } 129 | 130 | #[test] 131 | fn display() { 132 | let cs = CharacterString::from("www"); 133 | assert_eq!(cs.to_string(), "www"); 134 | } 135 | 136 | #[test] 137 | fn deserialize_from() { 138 | use type2network::FromNetworkOrder; 139 | let mut buffer = Cursor::new([0x06_u8, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65].as_slice()); 140 | let mut cs = CharacterString::default(); 141 | assert!(cs.deserialize_from(&mut buffer).is_ok()); 142 | assert_eq!(cs.length, 6u8); 143 | assert_eq!(std::str::from_utf8(&cs.data).unwrap(), "google"); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/dns/rfc/cname.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | // CNAME resource record 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct CNAME(DomainName); 13 | 14 | impl fmt::Display for CNAME { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | write!(f, "{}", self.0) 17 | } 18 | } 19 | 20 | pub type DNAME = CNAME; 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use crate::{ 25 | dns::rfc::{cname::DNAME, rdata::RData, response::Response}, 26 | dns::tests::get_packets, 27 | test_rdata, 28 | }; 29 | 30 | use type2network::FromNetworkOrder; 31 | 32 | use super::CNAME; 33 | 34 | test_rdata!( 35 | rdata_cname, 36 | "./tests/pcap/cname.pcap", 37 | false, 38 | 1, 39 | RData::CNAME, 40 | (|x: &CNAME, _| { 41 | assert_eq!(x.to_string(), "cname-txt.dns.netmeister.org."); 42 | }) 43 | ); 44 | 45 | test_rdata!( 46 | rdata, 47 | "./tests/pcap/dname.pcap", 48 | false, 49 | 1, 50 | RData::DNAME, 51 | (|x: &DNAME, _| { 52 | assert_eq!(&x.to_string(), "dns.netmeister.org."); 53 | }) 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/dns/rfc/csync.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // use log::trace; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use super::type_bitmaps::TypeBitMaps; 8 | 9 | use crate::new_rd_length; 10 | 11 | // https://www.rfc-editor.org/rfc/rfc7477.html#section-2.1 12 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 13 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // | SOA Serial | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // | Flags | Type Bit Map / 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // / Type Bit Map (continued) / 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | #[allow(clippy::upper_case_acronyms)] 22 | #[derive(Debug, Default, FromNetwork)] 23 | pub struct CSYNC { 24 | // transmistted through RR deserialization 25 | #[from_network(ignore)] 26 | pub(super) rd_length: u16, 27 | 28 | soa_serial: u32, 29 | flags: u16, 30 | 31 | #[from_network(with_code( self.types = TypeBitMaps::new(self.rd_length - 6); ))] 32 | types: TypeBitMaps, 33 | } 34 | 35 | // auto-implement new 36 | new_rd_length!(CSYNC); 37 | 38 | impl fmt::Display for CSYNC { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | write!(f, "{} {} {}", self.soa_serial, self.flags, self.types)?; 41 | 42 | Ok(()) 43 | } 44 | } 45 | 46 | // Custom serialization 47 | use serde::{ser::SerializeMap, Serialize, Serializer}; 48 | impl Serialize for CSYNC { 49 | fn serialize(&self, serializer: S) -> std::result::Result 50 | where 51 | S: Serializer, 52 | { 53 | let mut seq = serializer.serialize_map(Some(3))?; 54 | seq.serialize_entry("soa_serial", &self.soa_serial)?; 55 | seq.serialize_entry("flags", &self.flags)?; 56 | seq.serialize_entry("types", &self.types.to_string())?; 57 | seq.end() 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use crate::{ 64 | dns::rfc::{rdata::RData, response::Response}, 65 | dns::tests::get_packets, 66 | test_rdata, 67 | }; 68 | 69 | use type2network::FromNetworkOrder; 70 | 71 | use super::CSYNC; 72 | 73 | test_rdata!( 74 | rdata, 75 | "./tests/pcap/csync.pcap", 76 | false, 77 | 1, 78 | RData::CSYNC, 79 | (|x: &CSYNC, _| { 80 | assert_eq!(&x.to_string(), "2021071001 3 NS"); 81 | }) 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /src/dns/rfc/dhcid.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // use log::trace; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use serde::{Serialize, Serializer}; 8 | 9 | use crate::{dns::buffer::Buffer, new_rd_length}; 10 | 11 | // https://datatracker.ietf.org/doc/html/rfc4701#section-3.1 12 | #[derive(Debug, Default, FromNetwork)] 13 | pub struct DHCID { 14 | // transmistted through RR deserialization 15 | #[from_network(ignore)] 16 | pub(super) rd_length: u16, 17 | 18 | #[from_network(with_code( self.data = Buffer::with_capacity(self.rd_length); ))] 19 | data: Buffer, 20 | } 21 | 22 | // auto-implement new 23 | new_rd_length!(DHCID); 24 | 25 | impl fmt::Display for DHCID { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | write!(f, "{}", self.data.to_base64()) 28 | } 29 | } 30 | 31 | impl Serialize for DHCID { 32 | fn serialize(&self, serializer: S) -> std::result::Result 33 | where 34 | S: Serializer, 35 | { 36 | serializer.serialize_str(&self.data.to_base64()) 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use crate::{ 43 | dns::rfc::{rdata::RData, response::Response}, 44 | dns::tests::get_packets, 45 | test_rdata, 46 | }; 47 | 48 | use type2network::FromNetworkOrder; 49 | 50 | use super::DHCID; 51 | 52 | test_rdata!( 53 | rdata, 54 | "./tests/pcap/dhcid.pcap", 55 | false, 56 | 1, 57 | RData::DHCID, 58 | (|x: &DHCID, _| { 59 | assert_eq!(&x.to_string(), "AAIBMmFjOTc1NzMyMTk0ZWE1ZTBhN2MzN2M4MzE2NTFiM2M="); 60 | }) 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/dns/rfc/dname.rs: -------------------------------------------------------------------------------- 1 | //! Definition of the DNAME record (https://datatracker.ietf.org/doc/html/rfc6672#section-2.1) 2 | use std::fmt; 3 | 4 | // use log::trace; 5 | use type2network::FromNetworkOrder; 6 | use type2network_derive::FromNetwork; 7 | 8 | use serde::Serialize; 9 | 10 | use super::domain::DomainName; 11 | 12 | // https://www.rfc-editor.org/rfc/rfc7477.html#section-2.1 13 | // DNAME 14 | #[allow(clippy::upper_case_acronyms)] 15 | #[derive(Debug, Default, FromNetwork, Serialize)] 16 | pub struct DNAME<'a>(DomainName<'a>); 17 | 18 | impl<'a> fmt::Display for DNAME<'a> { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | write!(f, "{}", self.0)?; 21 | 22 | Ok(()) 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use crate::{ 29 | dns::rfc::{rdata::RData, response::Response}, 30 | test_rdata, 31 | dns::tests::get_packets, 32 | }; 33 | 34 | use type2network::FromNetworkOrder; 35 | 36 | use super::DNAME; 37 | 38 | test_rdata!( 39 | rdata, 40 | "./tests/pcap/dname.pcap", 41 | false, 42 | 1, 43 | RData::DNAME, 44 | (|x: &DNAME, _| { 45 | assert_eq!(&x.to_string(), "dns.netmeister.org."); 46 | }) 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/dns/rfc/ds.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use crate::{dns::buffer::Buffer, new_rd_length}; 7 | 8 | use super::algorithm::Algorithm; 9 | 10 | // The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet 11 | // Algorithm field, a 1 octet Digest Type field, and a Digest field. 12 | // 13 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 14 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 15 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | // | Key Tag | Algorithm | Digest Type | 17 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | // / / 19 | // / Digest / 20 | // / / 21 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | #[allow(clippy::upper_case_acronyms)] 23 | #[derive(Debug, Default, FromNetwork)] 24 | pub struct DS { 25 | #[from_network(ignore)] 26 | pub(super) rd_length: u16, 27 | 28 | key_tag: u16, 29 | algorithm: Algorithm, 30 | digest_type: u8, 31 | 32 | #[from_network(with_code( self.digest = Buffer::with_capacity(self.rd_length - 4); ))] 33 | pub(super) digest: Buffer, 34 | } 35 | 36 | // auto-implement new 37 | new_rd_length!(DS); 38 | 39 | impl fmt::Display for DS { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | write!( 42 | f, 43 | "{} {} {} {:?}", 44 | self.key_tag, self.algorithm, self.digest_type, self.digest 45 | ) 46 | } 47 | } 48 | 49 | // Custom serialization 50 | use serde::{ser::SerializeMap, Serialize, Serializer}; 51 | impl Serialize for DS { 52 | fn serialize(&self, serializer: S) -> std::result::Result 53 | where 54 | S: Serializer, 55 | { 56 | let mut seq = serializer.serialize_map(Some(4))?; 57 | seq.serialize_entry("key_tag", &self.key_tag)?; 58 | seq.serialize_entry("algorithm", &self.algorithm.to_string())?; 59 | seq.serialize_entry("digest_type", &self.digest_type)?; 60 | seq.serialize_entry("digest", &self.digest.to_base64())?; 61 | seq.end() 62 | } 63 | } 64 | 65 | #[allow(clippy::upper_case_acronyms)] 66 | pub(super) type DLV = DS; 67 | 68 | #[allow(clippy::upper_case_acronyms)] 69 | pub(super) type CDS = DS; 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use crate::{ 74 | dns::rfc::{rdata::RData, response::Response}, 75 | dns::tests::get_packets, 76 | test_rdata, 77 | }; 78 | 79 | use type2network::FromNetworkOrder; 80 | 81 | use super::DS; 82 | 83 | test_rdata!( 84 | rdata_ds, 85 | "./tests/pcap/ds.pcap", 86 | false, 87 | 1, 88 | RData::DS, 89 | (|x: &DS, _| { 90 | assert_eq!( 91 | &x.to_string(), 92 | "56393 ECDSAP256SHA256 2 BD36DD608262A02683721FA19E2F7B474F531BB3179CC0A0C38FF0CA11657" 93 | ); 94 | }) 95 | ); 96 | 97 | test_rdata!( 98 | rdata_dlv, 99 | "./tests/pcap/dlv.pcap", 100 | false, 101 | 1, 102 | RData::DLV, 103 | (|x: &DS, _| { 104 | assert_eq!( 105 | &x.to_string(), 106 | "56039 ECDSAP256SHA256 2 414805B43928FC573F0704A2C1B5A10BAA2878DE26B8535DDE77517C154CE9F" 107 | ); 108 | }) 109 | ); 110 | 111 | test_rdata!( 112 | rdata_cds, 113 | "./tests/pcap/cds.pcap", 114 | false, 115 | 1, 116 | RData::CDS, 117 | (|x: &DS, _| { 118 | assert_eq!( 119 | &x.to_string(), 120 | "56039 ECDSAP256SHA256 2 414805B43928FC573F0704A2C1B5A10BAA2878DE26B8535DDE77517C154CE9F" 121 | ); 122 | }) 123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /src/dns/rfc/eui48.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | // https://datatracker.ietf.org/doc/html/rfc7043#section-4 7 | // 0 1 2 3 8 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | // | EUI-64 Address | 11 | // | | 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | #[derive(Debug, Default, FromNetwork)] 14 | pub struct EUI48([u8; 6]); 15 | 16 | impl fmt::Display for EUI48 { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | let buf: Vec<_> = self.0.iter().map(|c| format!("{:x?}", c)).collect(); 19 | write!(f, "{}", buf.join("-")) 20 | } 21 | } 22 | 23 | // Custom serialization 24 | use serde::{Serialize, Serializer}; 25 | impl Serialize for EUI48 { 26 | fn serialize(&self, serializer: S) -> std::result::Result 27 | where 28 | S: Serializer, 29 | { 30 | serializer.serialize_str(&self.to_string()) 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use crate::{ 37 | dns::rfc::{rdata::RData, response::Response}, 38 | dns::tests::get_packets, 39 | test_rdata, 40 | }; 41 | 42 | use type2network::FromNetworkOrder; 43 | 44 | use super::EUI48; 45 | 46 | test_rdata!( 47 | rdata, 48 | "./tests/pcap/eui48.pcap", 49 | false, 50 | 1, 51 | RData::EUI48, 52 | (|x: &EUI48, _| { 53 | assert_eq!(&x.to_string(), "bc-a2-b9-82-32-a7"); 54 | }) 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/dns/rfc/eui64.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | // https://datatracker.ietf.org/doc/html/rfc7043#section-4 7 | // 0 1 2 3 8 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | // | EUI-64 Address | 11 | // | | 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | #[derive(Debug, Default, FromNetwork)] 14 | pub struct EUI64(u64); 15 | 16 | impl fmt::Display for EUI64 { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | let buf: Vec<_> = self.0.to_be_bytes().iter().map(|c| format!("{:x?}", c)).collect(); 19 | write!(f, "{}", buf.join("-")) 20 | } 21 | } 22 | 23 | // Custom serialization 24 | use serde::{Serialize, Serializer}; 25 | impl Serialize for EUI64 { 26 | fn serialize(&self, serializer: S) -> std::result::Result 27 | where 28 | S: Serializer, 29 | { 30 | serializer.serialize_str(&self.to_string()) 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use crate::{ 37 | dns::rfc::{rdata::RData, response::Response}, 38 | dns::tests::get_packets, 39 | test_rdata, 40 | }; 41 | 42 | use type2network::FromNetworkOrder; 43 | 44 | use super::EUI64; 45 | 46 | test_rdata!( 47 | rdata, 48 | "./tests/pcap/eui64.pcap", 49 | false, 50 | 1, 51 | RData::EUI64, 52 | (|x: &EUI64, _| { 53 | assert_eq!(&x.to_string(), "be-a2-b9-ff-fe-82-32-a7"); 54 | }) 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/dns/rfc/header.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use colored::Colorize; 4 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 5 | use type2network_derive::{FromNetwork, ToNetwork}; 6 | 7 | use rand::Rng; 8 | use serde::Serialize; 9 | 10 | use super::{flags::Flags, opcode::OpCode, packet_type::PacketType, response_code::ResponseCode}; 11 | 12 | // 1 1 1 1 1 1 13 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 14 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 15 | // | ID | 16 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 17 | // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | 18 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 19 | // | QDCOUNT | 20 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 21 | // | ANCOUNT | 22 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 23 | // | NSCOUNT | 24 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 25 | // | ARCOUNT | 26 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 27 | #[derive(Debug, Clone, ToNetwork, FromNetwork, Serialize)] 28 | pub struct Header { 29 | pub id: u16, // A 16 bit identifier assigned by the program that 30 | // generates any kind of query. This identifier is copied 31 | // the corresponding reply and can be used by the requester 32 | // to match up replies to outstanding queries. 33 | pub flags: Flags, 34 | pub qd_count: u16, // an unsigned 16 bit integer specifying the number of 35 | // entries in the question section. 36 | pub an_count: u16, // an unsigned 16 bit integer specifying the number of 37 | // resource records in the answer section. 38 | pub ns_count: u16, // an unsigned 16 bit integer specifying the number of name 39 | // server resource records in the authority records section. 40 | pub ar_count: u16, // an unsigned 16 bit integer specifying the number of 41 | // resource records in the additional records section. 42 | } 43 | 44 | impl Header { 45 | // DoQ must set ID to 0: https://datatracker.ietf.org/doc/rfc9250/ section 4.2.1 46 | pub fn set_id(&mut self, id: u16) { 47 | self.id = id; 48 | } 49 | 50 | pub fn set_response_code(&mut self, rc: ResponseCode) { 51 | self.flags.set_response_code(rc); 52 | } 53 | } 54 | 55 | impl Default for Header { 56 | fn default() -> Self { 57 | // by default, we use the recursion desired flag at query 58 | let flags = Flags { 59 | qr: PacketType::Query, 60 | op_code: OpCode::Query, 61 | ..Default::default() 62 | }; 63 | 64 | let mut rng = rand::thread_rng(); 65 | 66 | Self { 67 | id: rng.gen::(), 68 | flags, 69 | qd_count: 1, 70 | an_count: 0, 71 | ns_count: 0, 72 | ar_count: 0, 73 | } 74 | } 75 | } 76 | 77 | impl fmt::Display for Header { 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | write!(f, "{}:0x{:X}({}) ", "id".bright_cyan(), self.id, self.id)?; 80 | write!(f, "{}:<{}> ", "flags".bright_cyan(), self.flags)?; 81 | 82 | if self.flags.qr == PacketType::Query { 83 | write!(f, "{}:{}", "qd_count".bright_cyan(), self.qd_count) 84 | } else { 85 | write!( 86 | f, 87 | "qd_count:{}, an_count:{} ns_count:{} ar_count:{}", 88 | self.qd_count, self.an_count, self.ns_count, self.ar_count 89 | ) 90 | } 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | #[test] 97 | fn network() { 98 | use crate::dns::rfc::header::Header; 99 | use std::io::Cursor; 100 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 101 | 102 | let sample = vec![0x49, 0x1e, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]; 103 | let mut buffer = Cursor::new(sample.as_slice()); 104 | let mut h = Header::default(); 105 | assert!(h.deserialize_from(&mut buffer).is_ok()); 106 | //assert_eq!(h.flags, Flags::try_from(0x0120).unwrap()); 107 | assert_eq!(h.qd_count, 1); 108 | assert_eq!(h.an_count, 0); 109 | assert_eq!(h.ns_count, 0); 110 | assert_eq!(h.ar_count, 1); 111 | 112 | let mut buffer: Vec = Vec::new(); 113 | assert!(h.serialize_to(&mut buffer).is_ok()); 114 | assert_eq!(buffer, sample); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/dns/rfc/hinfo.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::char_string::CharacterString; 9 | 10 | // HINFO RR 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct HINFO { 13 | cpu: CharacterString, 14 | os: CharacterString, 15 | } 16 | 17 | impl fmt::Display for HINFO { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | write!(f, "{} {}", self.cpu, self.os) 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use crate::{ 26 | dns::rfc::{rdata::RData, response::Response}, 27 | dns::tests::get_packets, 28 | test_rdata, 29 | }; 30 | 31 | use type2network::FromNetworkOrder; 32 | 33 | use super::HINFO; 34 | 35 | test_rdata!( 36 | rdata, 37 | "./tests/pcap/hinfo.pcap", 38 | false, 39 | 1, 40 | RData::HINFO, 41 | (|x: &HINFO, _| { 42 | assert_eq!(&x.to_string(), "PDP-11 UNIX"); 43 | }) 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/dns/rfc/hip.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // use log::trace; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use crate::{dns::buffer::Buffer, new_rd_length}; 8 | 9 | // https://datatracker.ietf.org/doc/html/rfc5205.html#section-5 10 | // 0 1 2 3 11 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | // | HIP length | PK algorithm | PK length | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // | | 16 | // ~ HIP ~ 17 | // | | 18 | // + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // | | | 20 | // +-+-+-+-+-+-+-+-+-+-+-+ + 21 | // | Public Key | 22 | // ~ ~ 23 | // | | 24 | // + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | // | | | 26 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 27 | // | | 28 | // ~ Rendezvous Servers ~ 29 | // | | 30 | // + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 | // | | 32 | // +-+-+-+-+-+-+-+ 33 | #[derive(Debug, Default, FromNetwork)] 34 | pub struct HIP { 35 | // transmistted through RR deserialization 36 | #[from_network(ignore)] 37 | pub(super) rd_length: u16, 38 | 39 | hit_length: u8, 40 | pk_algorithm: u8, 41 | pk_length: u16, 42 | 43 | #[from_network(with_code( self.hit = Buffer::with_capacity(self.hit_length); ))] 44 | hit: Buffer, 45 | 46 | #[from_network(with_code( self.public_key = Buffer::with_capacity(self.pk_length); ))] 47 | public_key: Buffer, 48 | 49 | #[from_network(with_code( self.rendezvous_servers = Buffer::with_capacity(self.rd_length - 4 - self.hit_length as u16 - self.pk_length); ))] 50 | rendezvous_servers: Buffer, 51 | } 52 | 53 | // auto-implement new 54 | new_rd_length!(HIP); 55 | 56 | impl fmt::Display for HIP { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | write!( 59 | f, 60 | "{} {} {}", 61 | self.pk_algorithm, 62 | self.hit.to_base16(), 63 | self.public_key.to_base64() 64 | ) 65 | } 66 | } 67 | 68 | // Custom serialization 69 | use serde::{ser::SerializeMap, Serialize, Serializer}; 70 | impl Serialize for HIP { 71 | fn serialize(&self, serializer: S) -> std::result::Result 72 | where 73 | S: Serializer, 74 | { 75 | let mut seq = serializer.serialize_map(Some(3))?; 76 | seq.serialize_entry("pk_algorithm", &self.pk_algorithm)?; 77 | seq.serialize_entry("hit", &self.hit.to_base16())?; 78 | seq.serialize_entry("public_key", &self.public_key.to_base64())?; 79 | seq.end() 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use crate::{ 86 | dns::rfc::{rdata::RData, response::Response}, 87 | dns::tests::get_packets, 88 | test_rdata, 89 | }; 90 | 91 | use type2network::FromNetworkOrder; 92 | 93 | use super::HIP; 94 | 95 | test_rdata!( 96 | rdata, 97 | "./tests/pcap/hip.pcap", 98 | false, 99 | 1, 100 | RData::HIP, 101 | (|x: &HIP, _| { 102 | assert_eq!(&x.to_string(), "2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D"); 103 | }) 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /src/dns/rfc/kx.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 11 | // | PREFERENCE | 12 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 13 | // / EXCHANGER / 14 | // / / 15 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 16 | #[derive(Debug, Default, FromNetwork, Serialize)] 17 | pub struct KX { 18 | preference: u16, 19 | exchanger: DomainName, 20 | } 21 | 22 | impl fmt::Display for KX { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | write!(f, "{} {}", self.preference, self.exchanger) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use crate::{ 31 | dns::rfc::{rdata::RData, response::Response}, 32 | dns::tests::get_packets, 33 | test_rdata, 34 | }; 35 | 36 | use type2network::FromNetworkOrder; 37 | 38 | use super::KX; 39 | 40 | test_rdata!( 41 | rdata, 42 | "./tests/pcap/kx.pcap", 43 | false, 44 | 1, 45 | RData::KX, 46 | (|x: &KX, _| { 47 | assert_eq!(&x.to_string(), "1 panix.netmeister.org."); 48 | }) 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/dns/rfc/loc.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | // LOC RR (https://datatracker.ietf.org/doc/html/rfc1876) 9 | #[derive(Debug, Default, FromNetwork, Serialize)] 10 | pub struct LOC { 11 | pub(super) version: u8, 12 | pub(super) size: u8, 13 | pub(super) horiz_pre: u8, 14 | pub(super) vert_pre: u8, 15 | pub(super) latitude1: u16, 16 | pub(super) latitude2: u16, 17 | pub(super) longitude1: u16, 18 | pub(super) longitude2: u16, 19 | pub(super) altitude1: u16, 20 | pub(super) altitude2: u16, 21 | } 22 | 23 | impl fmt::Display for LOC { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | write!( 26 | f, 27 | "{} {} {} {} {} {} {} {} {} {}", 28 | self.version, 29 | self.size, 30 | self.horiz_pre, 31 | self.vert_pre, 32 | self.latitude1, 33 | self.latitude2, 34 | self.longitude1, 35 | self.longitude2, 36 | self.altitude1, 37 | self.altitude2, 38 | ) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use crate::{ 45 | dns::rfc::{rdata::RData, response::Response}, 46 | dns::tests::get_packets, 47 | test_rdata, 48 | }; 49 | 50 | use type2network::FromNetworkOrder; 51 | 52 | use super::LOC; 53 | 54 | test_rdata!( 55 | rdata, 56 | "./tests/pcap/loc.pcap", 57 | false, 58 | 1, 59 | RData::LOC, 60 | (|x: &LOC, _| { 61 | assert_eq!(&x.to_string(), "0 18 22 19 35005 44968 28703 37840 152 39528"); 62 | }) 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/dns/rfc/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use log::trace; 4 | 5 | #[allow(clippy::unnecessary_cast)] 6 | pub mod char_string; 7 | pub mod domain; 8 | // pub mod domain2; 9 | pub mod flags; 10 | pub mod header; 11 | pub mod opcode; 12 | pub mod packet_type; 13 | pub mod qclass; 14 | pub mod qtype; 15 | pub mod question; 16 | pub mod resource_record; 17 | pub mod response_code; 18 | 19 | // all RRs 20 | pub mod a; 21 | pub mod aaaa; 22 | pub mod afsdb; 23 | pub mod algorithm; 24 | pub mod apl; 25 | pub mod caa; 26 | pub mod cert; 27 | pub mod cname; 28 | pub mod csync; 29 | pub mod dhcid; 30 | pub mod dnskey; 31 | pub mod ds; 32 | pub mod eui48; 33 | pub mod eui64; 34 | pub mod hinfo; 35 | pub mod hip; 36 | pub mod ipseckey; 37 | pub mod kx; 38 | pub mod loc; 39 | pub mod mx; 40 | pub mod naptr; 41 | pub mod ns; 42 | pub mod nsec; 43 | pub mod nsec3; 44 | pub mod nsec3param; 45 | pub mod openpgpkey; 46 | pub mod opt; 47 | pub mod ptr; 48 | pub mod query; 49 | pub mod rdata; 50 | pub mod response; 51 | pub mod rp; 52 | pub mod rrlist; 53 | pub mod rrsig; 54 | pub mod soa; 55 | pub mod srv; 56 | pub mod sshfp; 57 | pub mod svcb; 58 | // pub mod tkey; 59 | pub mod tlsa; 60 | pub mod txt; 61 | pub mod type_bitmaps; 62 | pub mod uri; 63 | pub mod wallet; 64 | pub mod zonemd; 65 | 66 | // 67 | pub(crate) trait DataLength { 68 | fn size(&self) -> u16; 69 | } 70 | 71 | // a helper macro to generate the new() method for those struct having the rd_length field 72 | // helper macro to ease returning the internal DNS errors 73 | #[macro_export] 74 | macro_rules! new_rd_length { 75 | // this macro works also for struct with lifetimes 76 | // see: https://stackoverflow.com/questions/41603424/rust-macro-accepting-type-with-generic-parameters 77 | // length is transmitted when calling macro get_rr!() but is already deserialized 78 | ($rr:ident $(< $lf:lifetime >)? ) => { 79 | impl $(< $lf >)? $rr $(< $lf >)? { 80 | pub fn new(len: u16) -> Self { 81 | log::trace!("new_rd_length!({}): receive length {}", stringify!($rr), len); 82 | 83 | let mut x = Self::default(); 84 | x.rd_length = len; 85 | 86 | x 87 | } 88 | } 89 | }; 90 | } 91 | 92 | // helper function to display currenr cursor data 93 | pub fn cursor_view(buffer: &Cursor<&[u8]>, length: usize) { 94 | let pos = buffer.position() as usize; 95 | let reference = buffer.get_ref(); 96 | trace!("buffer length: {}", reference.len()); 97 | trace!("cursor view at {}: {:X?}", pos, &reference[pos..pos + length]); 98 | } 99 | -------------------------------------------------------------------------------- /src/dns/rfc/mx.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | // MX RR 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct MX { 13 | pub preference: u16, // A 16 bit integer which specifies the preference given to 14 | // this RR among others at the same owner. Lower values 15 | // are preferred. 16 | pub exchange: DomainName, // A which specifies a host willing to act as a mail exchange for the owner name. 17 | } 18 | 19 | impl fmt::Display for MX { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!(f, "{} {}", self.preference, self.exchange) 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use crate::{ 28 | dns::rfc::{rdata::RData, response::Response}, 29 | dns::tests::get_packets, 30 | test_rdata, 31 | }; 32 | 33 | use type2network::FromNetworkOrder; 34 | 35 | use super::MX; 36 | 37 | test_rdata!( 38 | rdata, 39 | "./tests/pcap/mx.pcap", 40 | false, 41 | 1, 42 | RData::MX, 43 | (|x: &MX, _| { 44 | assert_eq!(&x.to_string(), "50 panix.netmeister.org."); 45 | }) 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/dns/rfc/naptr.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::{char_string::CharacterString, domain::DomainName}; 9 | 10 | // 1 1 1 1 1 1 11 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 12 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 13 | // | ORDER | 14 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 15 | // | PREFERENCE | 16 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 17 | // / FLAGS / 18 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 19 | // / SERVICES / 20 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 21 | // / REGEXP / 22 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 23 | // / REPLACEMENT / 24 | // / / 25 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 26 | #[allow(clippy::upper_case_acronyms)] 27 | #[derive(Debug, Default, FromNetwork, Serialize)] 28 | pub struct NAPTR { 29 | order: u16, 30 | preference: u16, 31 | flags: CharacterString, 32 | services: CharacterString, 33 | regex: CharacterString, 34 | replacement: DomainName, 35 | } 36 | 37 | impl fmt::Display for NAPTR { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | write!( 40 | f, 41 | "{} {} \"{}\" \"{}\" \"{}\" {}", 42 | self.order, self.preference, self.flags, self.services, self.regex, self.replacement 43 | ) 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use crate::{ 50 | dns::rfc::{rdata::RData, response::Response}, 51 | dns::tests::get_packets, 52 | test_rdata, 53 | }; 54 | 55 | use type2network::FromNetworkOrder; 56 | 57 | use super::NAPTR; 58 | 59 | test_rdata!( 60 | rdata, 61 | "./tests/pcap/naptr.pcap", 62 | false, 63 | 1, 64 | RData::NAPTR, 65 | (|x: &NAPTR, i: usize| { 66 | match i { 67 | 0 => assert_eq!( 68 | x.to_string(), 69 | "10 10 \"u\" \"smtp+E2U\" \"!.*([^.]+[^.]+)$!mailto:postmaster@$1!i\" ." 70 | ), 71 | 1 => assert_eq!( 72 | x.to_string(), 73 | "20 10 \"s\" \"http+N2L+N2C+N2R\" \"\" www.netmeister.org." 74 | ), 75 | _ => panic!("data not is the pcap file"), 76 | } 77 | }) 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/dns/rfc/ns.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | // NS resource record 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct NS(pub DomainName); 13 | 14 | impl fmt::Display for NS { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | write!(f, "{}", self.0) 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use crate::{ 23 | dns::rfc::{rdata::RData, response::Response}, 24 | dns::tests::get_packets, 25 | test_rdata, 26 | }; 27 | 28 | use type2network::FromNetworkOrder; 29 | 30 | use super::NS; 31 | 32 | test_rdata!( 33 | rdata, 34 | "./tests/pcap/ns.pcap", 35 | false, 36 | 1, 37 | RData::NS, 38 | (|x: &NS, _| { 39 | assert_eq!(&x.to_string(), "panix.netmeister.org."); 40 | }) 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/dns/rfc/nsec.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // use log::trace; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use serde::Serialize; 8 | 9 | use crate::new_rd_length; 10 | 11 | use super::{domain::DomainName, type_bitmaps::TypeBitMaps}; 12 | 13 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 14 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 15 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | // | Hash Alg. | Flags | Iterations | 17 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | // | Salt Length | Salt / 19 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | #[allow(clippy::upper_case_acronyms)] 21 | #[derive(Debug, Default, FromNetwork, Serialize)] 22 | pub struct NSEC { 23 | // transmistted through RR deserialization 24 | #[from_network(ignore)] 25 | rd_length: u16, 26 | 27 | domain: DomainName, 28 | 29 | #[from_network(with_code( self.types = TypeBitMaps::new(self.rd_length - self.domain.len() as u16); ))] 30 | types: TypeBitMaps, 31 | } 32 | 33 | // auto-implement new 34 | new_rd_length!(NSEC); 35 | 36 | impl fmt::Display for NSEC { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | write!(f, "{} {}", self.domain, self.types) 39 | } 40 | } 41 | 42 | // Custom serialization 43 | // use serde::{ser::SerializeMap, Serialize, Serializer}; 44 | // impl Serialize for NSEC { 45 | // fn serialize(&self, serializer: S) -> std::result::Result 46 | // where 47 | // S: Serializer, 48 | // { 49 | // let mut seq = serializer.serialize_map(Some(2))?; 50 | // seq.serialize_entry("domain", &self.domain)?; 51 | // seq.serialize_entry("protocol", &self.protocol)?; 52 | // seq.serialize_entry("algorithm", &self.algorithm.to_string())?; 53 | // seq.serialize_entry("key", &self.key.as_b64())?; 54 | // seq.end() 55 | // } 56 | // } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use crate::{ 61 | dns::rfc::{rdata::RData, response::Response}, 62 | dns::tests::get_packets, 63 | test_rdata, 64 | }; 65 | 66 | use type2network::FromNetworkOrder; 67 | 68 | use super::NSEC; 69 | 70 | test_rdata!( 71 | rdata, 72 | "./tests/pcap/nsec.pcap", 73 | false, 74 | 1, 75 | RData::NSEC, 76 | (|x: &NSEC, _| { 77 | assert_eq!(&x.to_string(), "nsec3.dns.netmeister.org. TXT RRSIG NSEC"); 78 | }) 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/dns/rfc/nsec3.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // use log::trace; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use serde::Serialize; 8 | 9 | use crate::{ 10 | dns::buffer::{serialize_buffer, Buffer}, 11 | // error::{Dns, Error}, 12 | new_rd_length, 13 | }; 14 | 15 | use super::{nsec3param::NSEC3PARAM, type_bitmaps::TypeBitMaps}; 16 | 17 | //------------------------------------------------------------------------------------- 18 | // NSEC3 depends on NSEC3PARAM 19 | //------------------------------------------------------------------------------------- 20 | 21 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 22 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 23 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | // | Hash Alg. | Flags | Iterations | 25 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | // | Salt Length | Salt / 27 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | // | Hash Length | Next Hashed Owner Name / 29 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | // / Type Bit Maps / 31 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | #[derive(Debug, Default, FromNetwork, Serialize)] 33 | pub struct NSEC3 { 34 | // transmistted through RR deserialization 35 | #[serde(skip_serializing)] 36 | #[from_network(ignore)] 37 | pub(super) rd_length: u16, 38 | 39 | #[serde(flatten)] 40 | params: NSEC3PARAM, 41 | hash_length: u8, 42 | 43 | #[serde(serialize_with = "serialize_buffer")] 44 | #[from_network(with_code( self.owner_name = Buffer::with_capacity(self.hash_length); ))] 45 | owner_name: Buffer, 46 | 47 | #[from_network(with_code( self.types = TypeBitMaps::new(self.rd_length - (self.params.len() + 1 + self.hash_length as usize) as u16); ))] 48 | types: TypeBitMaps, 49 | } 50 | 51 | // auto-implement new 52 | new_rd_length!(NSEC3); 53 | 54 | impl fmt::Display for NSEC3 { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | write!(f, "{} {:?} ", self.params, self.owner_name)?; 57 | 58 | for qt in &self.types.types { 59 | write!(f, "{} ", qt)?; 60 | } 61 | 62 | Ok(()) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use crate::{ 69 | dns::{ 70 | rfc::{rdata::RData, response::Response}, 71 | tests::get_packets, 72 | }, 73 | error::{Dns, Error}, 74 | }; 75 | 76 | use type2network::FromNetworkOrder; 77 | 78 | #[test] 79 | fn rdata() -> crate::error::Result<()> { 80 | { 81 | // extract response packet 82 | let data = get_packets("./tests/pcap/nsec3.pcap", 0, 1); 83 | 84 | // manage TCP length if any 85 | let mut resp_buffer = std::io::Cursor::new(&data.1[0x2A..]); 86 | 87 | println!("{:X?}", resp_buffer); 88 | 89 | let mut resp = Response::default(); 90 | resp.deserialize_from(&mut resp_buffer) 91 | .map_err(|_| Error::Dns(Dns::CantDeserialize))?; 92 | 93 | let answer = resp.authority().as_ref().unwrap(); 94 | 95 | for (i, a) in answer.iter().enumerate() { 96 | match i { 97 | 0 => { 98 | if let RData::SOA(x) = &a.r_data { 99 | assert_eq!( 100 | &x.to_string(), 101 | "panix.netmeister.org. jschauma.netmeister.org. 2021073555 3600 300 3600000 3600" 102 | ); 103 | } 104 | } 105 | 1 => { 106 | if let RData::RRSIG(x) = &a.r_data { 107 | assert_eq!(&x.to_string(), "SOA ECDSAP256SHA256 nsec3.dns.netmeister.org. 20240114141115 20231231131115 24381 5JizDTGokTMjAuzVYm27HH6STw70v8Hz8lS+QVHTpsxnQJhgCK2HjvSKtf/hnUgZKJ8ywNH9XTfBHK1oCrHxSQ=="); 108 | } 109 | } 110 | 2 => { 111 | if let RData::NSEC3(x) = &a.r_data { 112 | assert_eq!(&x.to_string(), "1 0 15 508B7248F76E19FD AD409ACAD23C99B998D437318235167D65A06BDE NS SOA TXT RRSIG DNSKEY NSEC3PARAM CDS CDNSKEY "); 113 | } 114 | } 115 | 3 => { 116 | if let RData::RRSIG(x) = &a.r_data { 117 | assert_eq!(&x.to_string(), "NSEC3 ECDSAP256SHA256 nsec3.dns.netmeister.org. 20240109015350 20231226011049 24381 fTlRgL8n2BFwxMguZv1ASryNtCn9O9LhdsVqkZiPc8fTP8777QkHZsofNujVN93/+EcjqYPtfXibAyETUnC3jA=="); 118 | } 119 | } 120 | _ => panic!("unexpected pcap answer"), 121 | } 122 | } 123 | 124 | Ok(()) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/dns/rfc/nsec3param.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | //use type2network_derive::FromNetwork; 6 | 7 | use crate::dns::buffer::Buffer; 8 | 9 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 10 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 11 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | // | Hash Alg. | Flags | Iterations | 13 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | // | Salt Length | Salt / 15 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | #[allow(clippy::len_without_is_empty)] 17 | #[derive(Debug, Default, FromNetwork)] 18 | pub struct NSEC3PARAM { 19 | algorithm: u8, 20 | flags: u8, 21 | iterations: u16, 22 | salt_length: u8, 23 | 24 | #[from_network(with_code( self.salt = Buffer::with_capacity(self.salt_length); ))] 25 | salt: Buffer, 26 | } 27 | 28 | impl NSEC3PARAM { 29 | pub fn len(&self) -> usize { 30 | 5usize + self.salt_length as usize 31 | } 32 | } 33 | 34 | impl fmt::Display for NSEC3PARAM { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!( 37 | f, 38 | "{} {} {} {:?}", 39 | self.algorithm, self.flags, self.iterations, self.salt 40 | )?; 41 | Ok(()) 42 | } 43 | } 44 | 45 | // Custom serialization 46 | use serde::{ser::SerializeMap, Serialize, Serializer}; 47 | impl Serialize for NSEC3PARAM { 48 | fn serialize(&self, serializer: S) -> std::result::Result 49 | where 50 | S: Serializer, 51 | { 52 | let mut seq = serializer.serialize_map(Some(4))?; 53 | seq.serialize_entry("algorithm", &self.algorithm)?; 54 | seq.serialize_entry("flags", &self.flags)?; 55 | seq.serialize_entry("iterations", &self.iterations)?; 56 | seq.serialize_entry("salt", &self.salt.to_hex())?; 57 | seq.end() 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use crate::{ 64 | dns::rfc::{rdata::RData, response::Response}, 65 | dns::tests::get_packets, 66 | test_rdata, 67 | }; 68 | 69 | use type2network::FromNetworkOrder; 70 | 71 | use super::NSEC3PARAM; 72 | 73 | test_rdata!( 74 | rdata, 75 | "./tests/pcap/nsec3param.pcap", 76 | false, 77 | 1, 78 | RData::NSEC3PARAM, 79 | (|x: &NSEC3PARAM, _| { 80 | assert_eq!(&x.to_string(), "1 0 15 CB49105466D36AD"); 81 | }) 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /src/dns/rfc/opcode.rs: -------------------------------------------------------------------------------- 1 | use byteorder::ReadBytesExt; 2 | use serde::Serialize; 3 | 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use enum_from::{EnumDisplay, EnumTryFrom}; 8 | 9 | /// op codes: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5 10 | #[derive(Debug, Default, Clone, Copy, PartialEq, FromNetwork, EnumTryFrom, EnumDisplay, Serialize)] 11 | #[repr(u8)] 12 | #[from_network(TryFrom)] 13 | pub enum OpCode { 14 | #[default] 15 | Query = 0, //[RFC1035] 16 | IQuery = 1, // (Inverse Query, OBSOLETE) [RFC3425] 17 | Status = 2, // [RFC1035] 18 | Unassigned = 3, 19 | Notify = 4, // [RFC1996] 20 | Update = 5, // [RFC2136] 21 | DOS = 6, // DNS Stateful Operations (DSO) [RFC8490] 22 | // 7-15 Unassigned 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | use crate::dns::tests::from_network_test; 29 | 30 | #[test] 31 | fn network() { 32 | let q = OpCode::DOS; 33 | from_network_test(None, &q, &vec![0x06]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/dns/rfc/openpgpkey.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use crate::{dns::buffer::Buffer, new_rd_length}; 7 | 8 | //------------------------------------------------------------------------------------- 9 | // OPENPGPKEY 10 | //------------------------------------------------------------------------------------- 11 | #[allow(clippy::upper_case_acronyms)] 12 | #[derive(Debug, Default, FromNetwork)] 13 | pub struct OPENPGPKEY { 14 | // transmistted through RR deserialization 15 | #[from_network(ignore)] 16 | rd_length: u16, 17 | 18 | #[from_network(with_code( self.key = Buffer::with_capacity(self.rd_length); ))] 19 | key: Buffer, 20 | } 21 | 22 | // auto-implement new 23 | new_rd_length!(OPENPGPKEY); 24 | 25 | impl fmt::Display for OPENPGPKEY { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | write!(f, "{}", self.key.to_base64()) 28 | } 29 | } 30 | 31 | // Custom serialization 32 | use serde::{Serialize, Serializer}; 33 | impl Serialize for OPENPGPKEY { 34 | fn serialize(&self, serializer: S) -> std::result::Result 35 | where 36 | S: Serializer, 37 | { 38 | serializer.serialize_str(&self.key.to_base64()) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use crate::{ 45 | dns::rfc::{rdata::RData, response::Response}, 46 | dns::tests::get_packets, 47 | test_rdata, 48 | }; 49 | 50 | use type2network::FromNetworkOrder; 51 | 52 | use super::OPENPGPKEY; 53 | 54 | test_rdata!( 55 | rdata, 56 | "./tests/pcap/openpgpkey.pcap", 57 | true, 58 | 7, 59 | RData::OPENPGPKEY, 60 | (|x: &OPENPGPKEY, _| { 61 | assert_eq!( 62 | &x.to_string(), 63 | r#"mQENBE2L+QkBCADx6DXFdqDEAK1OYYtOeLp54Z0G87t6Nmz+nodbd9f4Uw0T6v32O2O0yVwA07fCGfPc+3oeCgDact5cpicAm1C1nF3XrcV6YCAccswybl11ZnlJBOtu1iePYHoBM+iZwdtCaPVlnPoFbuYbjDt5sv7g1MN5sXqktkyEg8JcJKWxrlaFI0lH/YIpOBokXznv2YUWIg+8V6GTGpX2kYRJziXJizzQ1jFYn1UP3Pa+PYlffkbT/vEaLc3NzVoLUavXRgeRrUWbDc06tQmYolZGArrH7Lrf6Bft1YFNsTxXqo/eUFvW8gURAxbbD9F05sFtyDenuVl40xsbMfSFtqfQKi+TABEBAAG0I0phbiBTY2hhdW1hbm4gPGpzY2hhdW1hQG5ldGJzZC5vcmc+iQE2BBMBAgAgAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AFAmA1JXgACgkQZs5P6W9r09cPFgf8DfO2IGx1iIbrTHRM5K+KpifygRxJTckO+G1M9XICbO2DZ5O/eex0cFPaueSln92xp9skl5p2R3oIUVnSEaS00mGV7CMbKGIXlb4K4qeVb6uT8/2OCAn3xdPKehcW8lvguaS+65596XVLYjabz8ZhwhkxSL5XRbIPCga4AxVAi0DiJLLrEFPlRWb5X3VYdxxnU8lXiQKgAKWVhONldf2NZW8iOhGXVNXZMmjybFYihFdGO3szaZDFkeh96e2axE8BoXLxDuuTIe+F92oE6pWaH/asIo4LiGGYFdH/+2wqieoG1uNIQ5xc5xSju8qpdrQ4Q7GgeemF0A4CspKx5cMs8LQzSmFuIFNjaGF1bWFubiAoQGpzY2hhdW1hKSA8anNjaGF1bWFAbmV0bWVpc3Rlci5vcmc+iQE2BBMBAgAgBQJSrcFAAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQZs5P6W9r09efjgf/ajHLyvaVMeX2eT5V5tluecR2+ZKF0fPp1kV/kN2ilo1ikS4lClxzYf5mcBket+4TjfrDtVgRdipCszeYXerFBz3j554dORMTSxu3wItycL85nAbmdk7wH0uvNu4LN/rSxzg938oMp2O7gH9oZTx+mVczYW8I4I9RFttIvDjmAEujKzmI07kUJZsQCAtQ7jEEQRGHDggLv7hQI90tihunYbwfxmBnWNETD/mLkiouMwzfjVDHeC6GQok8oMiMf0RuGc2jmGZFqOAUGupBMIoDTJO5Mcn963va1Y4ncJBV+XEh9p7VfOSjc7bHfTSlFB/kaq4lSjQ8LLzYN1gfAdYU4rQnSmFuIFNjaGF1bWFubiA8anNjaGF1bWFAbmV0bWVpc3Rlci5vcmc+iQE5BBMBAgAjAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AFAmA1JXwCGQEACgkQZs5P6W9r09fGBwf+P2cm/HxfnlYhFS5hsNdbK0EyiXIewOYHkBj4ZkNlWvzNjwROZySEizF6Zfcxt8vZKCJckneAHrRNB6dXZSJ7S9Me0gPOS7AVYtX+5oJPZv4ug3odygJx0bXx/YTQQxoYHj4QG9Kxx+QXfHTZ6QkQ4Vc/gWMsTxmhFj3DSqyjPcLp5GSC6z6Pwpp5XlC1ctQwg2QvMxNcpjlvdbBF26QgQeKM45D41/W8cRyk2geZjZLI/8MiHsfQ88wCtuECjAGNfBDz/fNqjQ9a1M38Tad6WIsN+SZiX5aG6JrPRT9lL38U4/ziaA5WLSvBBvfc/jOoPQOtEK9UXkFutJmkaKQmQbkBDQRNi/kxAQgAtb4+nY+l5ojJpUWFOOMCGjvYO6PhY5NpuOfLIgjOHVcwj6Yz0LSnDG+QSnQ1JxSDovXxZZtcnN7j9xqJFbtMi4MQEoSNL3XxFZy+QAqqKNkGhARqW5uK4jlm5BPgza4qnaG5bqtdPMIOyvojIJQoWKhKcGMmWsvq3sD4JdMEsnK/YjQCH6N4eCos2P7nW6Q8kjMIO3YqJT+6sHliOXrqi5/4EoT6GmkyTttX5IMkClv4faAi7U9SkucZDjsdk2uwcetobUu/0LLnzFrexk/K2xNSDcX6MMD3x3/So1DsA6Mxo/FbwzE+AQ2Y2ve4Y9hGFX35TDoBi881kQ7oDiukLwARAQABiQEfBBgBAgAJBQJNi/kxAhsMAAoJEGbOT+lva9PXpWAIAMn/iaZdax6a0GkEkPWvwpzb1zjNehjnO5lKI4NrLKNlygHoWL4SXsr925e/GOFInAn6iGdB3KibE8YEoWVuON5teMMsZxfln094F5szTv1HA8Gsdvf0R+8IMifFO+7HavJj+Qhuu8+Xpm8tleYeZR61qbY4h4KoPQP4G4KbF+R11vma31gLkBGD5gnkgVPyhFuPeBptCP+T+2W9sc2EEVcxWbLB0qcqyBEy6eXiPxyKurOCed9kBvyqo+FZTJpElOnJo/NqodY5Nsz1QchbMHN2FVmmFfrVpocnRQPm1lxqzxwoqJrUTyWpk/J8/0PbKlSTjRKziFLqudSy/dqFWmk="# 64 | ); 65 | }) 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/client_subnet.rs: -------------------------------------------------------------------------------- 1 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 2 | use type2network_derive::{FromNetwork, ToNetwork}; 3 | 4 | use serde::Serialize; 5 | 6 | use crate::dns::buffer::Buffer; 7 | 8 | // https://www.rfc-editor.org/rfc/rfc7871 9 | #[derive(Debug, Default, ToNetwork, FromNetwork, Serialize)] 10 | pub struct ClientSubnet { 11 | pub(super) family: u16, 12 | pub(super) source_prefix_length: u8, 13 | pub(super) scope_prefix_length: u8, 14 | pub(super) address: Buffer, 15 | } 16 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/cookie.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, num::ParseIntError}; 2 | 3 | use type2network::ToNetworkOrder; 4 | use type2network_derive::ToNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::{ 9 | opt_rr::{OptionCode, OptionData}, 10 | OptionDataValue, 11 | }; 12 | use crate::{opt_code, opt_data, opt_len}; 13 | 14 | // Cookie: https://www.rfc-editor.org/rfc/rfc7873 15 | // https://www.rfc-editor.org/rfc/rfc9018 16 | #[derive(Debug, Default, ToNetwork, Serialize)] 17 | pub struct COOKIE { 18 | pub client_cookie: [u8; 8], 19 | pub server_cookie: Option>, 20 | } 21 | 22 | impl COOKIE { 23 | // prepare a random cookie 24 | pub fn random() -> Self { 25 | Self { 26 | client_cookie: rand::random(), 27 | server_cookie: None, 28 | } 29 | } 30 | } 31 | 32 | impl From<&str> for COOKIE { 33 | fn from(cookie_string: &str) -> Self { 34 | match cookie_string.len() { 35 | // cookie is either empty, or less than 16 chars 36 | 0..=16 => COOKIE::random(), 37 | 38 | // otherwise take only 16 chars 39 | _ => { 40 | let mut cookie = COOKIE::default(); 41 | 42 | // if hex encoding is successful 43 | if let Ok(v) = decode_cookie(cookie_string) { 44 | cookie.client_cookie[0] = v[0]; 45 | cookie.client_cookie[1] = v[1]; 46 | cookie.client_cookie[2] = v[2]; 47 | cookie.client_cookie[3] = v[3]; 48 | cookie.client_cookie[4] = v[4]; 49 | cookie.client_cookie[5] = v[5]; 50 | cookie.client_cookie[6] = v[6]; 51 | cookie.client_cookie[7] = v[7]; 52 | 53 | cookie 54 | } 55 | // error, so fall back to a random one 56 | else { 57 | COOKIE::random() 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | impl fmt::Display for COOKIE { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | write!(f, "{:?} {:?}", self.client_cookie, self.server_cookie) 67 | } 68 | } 69 | 70 | impl OptionDataValue for COOKIE { 71 | // return the option code for the option data 72 | opt_code!(COOKIE); 73 | 74 | // return option data length 75 | opt_len!(8); 76 | 77 | // return None 78 | opt_data!(COOKIE); 79 | } 80 | 81 | // encode an hex string into a vector 82 | fn decode_cookie(s: &str) -> Result, ParseIntError> { 83 | (0..16) 84 | .step_by(2) 85 | .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) 86 | .collect() 87 | } 88 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/dau_dhu_n3u.rs: -------------------------------------------------------------------------------- 1 | use type2network::ToNetworkOrder; 2 | use type2network_derive::ToNetwork; 3 | 4 | use serde::Serialize; 5 | 6 | use crate::{opt_code, opt_data}; 7 | 8 | use super::{ 9 | opt_rr::{OptionData, OptionCode}, 10 | OptionDataValue, 11 | }; 12 | 13 | // useful macro to auto define DAU, DHU & N3U which are the same 14 | // https://www.rfc-editor.org/rfc/rfc6975.html 15 | macro_rules! opt { 16 | ($opt:ident, $t:ty) => { 17 | #[derive(Debug, Default, ToNetwork, Serialize)] 18 | pub struct $opt(Vec<$t>); 19 | 20 | impl From<&[$t]> for $opt { 21 | fn from(buf: &[$t]) -> Self { 22 | Self(buf.to_vec()) 23 | } 24 | } 25 | 26 | impl std::ops::Deref for $opt { 27 | type Target = Vec<$t>; 28 | 29 | fn deref(&self) -> &Self::Target { 30 | &self.0 31 | } 32 | } 33 | 34 | impl OptionData for $opt { 35 | // return the option code for the option data 36 | opt_code!($opt); 37 | 38 | // return option data length 39 | fn len(&self) -> u16 { 40 | self.0.len() as u16 41 | } 42 | 43 | // return the option data enum arm 44 | opt_data!($opt); 45 | } 46 | }; 47 | } 48 | 49 | // https://www.rfc-editor.org/rfc/rfc6975.html 50 | // impl DAU, DHU, N3U 51 | opt!(DAU, u8); 52 | opt!(DHU, u8); 53 | opt!(N3U, u8); 54 | 55 | // impl edns-key-tag 56 | opt!(EdnsKeyTag, u16); 57 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/extended.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::ToNetworkOrder; 4 | use type2network_derive::ToNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use crate::dns::buffer::Buffer; 9 | 10 | // https://www.rfc-editor.org/rfc/rfc7871 11 | #[derive(Debug, Default, ToNetwork, Serialize)] 12 | pub struct Extended { 13 | pub(super) info_code: u16, 14 | pub(super) extra_text: Buffer, 15 | } 16 | 17 | impl From<(u16, Buffer)> for Extended { 18 | fn from(x: (u16, Buffer)) -> Self { 19 | Self { 20 | info_code: x.0, 21 | extra_text: x.1, 22 | } 23 | } 24 | } 25 | 26 | impl fmt::Display for Extended { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | match self.info_code { 29 | 1 => write!(f, "Other"), 30 | 2 => write!(f, "Unsupported DNSKEY Algorithm"), 31 | 3 => write!(f, "Unsupported DS Digest Type"), 32 | 4 => write!(f, "Stale Answer"), 33 | 5 => write!(f, "Forged Answer"), 34 | 6 => write!(f, "DNSSEC Indeterminate"), 35 | 7 => write!(f, "DNSSEC Bogus"), 36 | 8 => write!(f, "Signature Expired"), 37 | 9 => write!(f, "Signature Not Yet Valid"), 38 | 10 => write!(f, "DNSKEY Missing"), 39 | 11 => write!(f, "RRSIGs Missing"), 40 | 12 => write!(f, "No Zone Key Bit Set"), 41 | 13 => write!(f, "NSEC Missing"), 42 | 14 => write!(f, "Cached Error"), 43 | 15 => write!(f, "Not Ready"), 44 | 16 => write!(f, "Blocked"), 45 | 17 => write!(f, "Censored"), 46 | 18 => write!(f, "Filtered"), 47 | 19 => write!(f, "Prohibited"), 48 | 20 => write!(f, "Stale NXDOMAIN Answer"), 49 | 21 => write!(f, "Not Authoritative"), 50 | 22 => write!(f, "Not Supported"), 51 | 23 => write!(f, "No Reachable Authority"), 52 | 24 => write!(f, "Network Error"), 53 | 25 => write!(f, "Invalid Data"), 54 | _ => write!(f, "extended code {} not yet assigned", self.info_code), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/llq.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 4 | use type2network_derive::{FromNetwork, ToNetwork}; 5 | 6 | use serde::Serialize; 7 | 8 | use super::{ 9 | opt_rr::{OptionCode, OptionData}, 10 | OptionDataValue, 11 | }; 12 | use crate::{opt_code, opt_data, opt_len}; 13 | 14 | // https://datatracker.ietf.org/doc/html/rfc8764 15 | #[derive(Debug, Default, ToNetwork, FromNetwork, Serialize)] 16 | pub struct LLQ { 17 | pub(super) version: u16, 18 | pub(super) opcode: u16, 19 | pub(super) error: u16, 20 | pub(super) id: u64, 21 | pub(super) lease: u32, 22 | } 23 | 24 | impl fmt::Display for LLQ { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | write!( 27 | f, 28 | "{} {} {} {} {}", 29 | self.version, self.opcode, self.error, self.id, self.lease 30 | ) 31 | } 32 | } 33 | 34 | impl OptionDataValue for LLQ { 35 | // return the option code for the option data 36 | opt_code!(LLQ); 37 | 38 | // return option data length 39 | opt_len!(18); 40 | 41 | // return None 42 | opt_data!(LLQ); 43 | } 44 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/mod.rs: -------------------------------------------------------------------------------- 1 | // Value Name Status Reference 2 | // 8 edns-client-subnet Optional [RFC7871] 3 | // 9 EDNS EXPIRE Optional [RFC7314] 4 | // 10 COOKIE Standard [RFC7873] 5 | // 11 edns-tcp-keepalive Standard [RFC7828] 6 | // 13 CHAIN Standard [RFC7901] 7 | // 15 Extended DNS Error Standard [RFC8914] 8 | // 16 EDNS-Client-Tag Optional [draft-bellis-dnsop-edns-tags] 9 | // 17 EDNS-Server-Tag Optional [draft-bellis-dnsop-edns-tags] 10 | // 18-20291 Unassigned 11 | // 20292 Umbrella Ident Optional [https://developer.cisco.com/docs/cloud-security/#!integrating-network-devices/rdata-description][Cisco_CIE_DNS_team] 12 | // 20293-26945 Unassigned 13 | // 26946 DeviceID Optional [https://developer.cisco.com/docs/cloud-security/#!network-devices-getting-started/response-codes][Cisco_CIE_DNS_team] 14 | // 26947-65000 Unassigned 15 | // 65001-65534 Reserved for Local/Experimental Use [RFC6891] 16 | // 65535 Reserved for future expansion [RFC6891] 17 | 18 | use self::opt_rr::{OptionCode, OptionData}; 19 | 20 | pub mod client_subnet; 21 | pub mod cookie; 22 | //pub mod dau_dhu_n3u; 23 | pub mod extended; 24 | pub mod llq; 25 | pub mod nsid; 26 | pub mod opt_rr; 27 | pub mod padding; 28 | pub mod report_chanel; 29 | pub mod zoneversion; 30 | 31 | pub trait OptionDataValue { 32 | // return the option code for the option data 33 | fn code(&self) -> OptionCode; 34 | 35 | // return option data length 36 | fn len(&self) -> u16; 37 | 38 | fn is_empty(&self) -> bool { 39 | self.len() == 0 40 | } 41 | 42 | // return the option data enum arm 43 | fn data(self) -> Option; 44 | } 45 | 46 | // macro helpers to define code() et data() easily 47 | #[macro_export] 48 | macro_rules! opt_code { 49 | ($code:ident) => { 50 | fn code(&self) -> OptionCode { 51 | OptionCode::$code 52 | } 53 | }; 54 | } 55 | 56 | #[macro_export] 57 | macro_rules! opt_len { 58 | ($length:literal) => { 59 | fn len(&self) -> u16 { 60 | $length 61 | } 62 | }; 63 | } 64 | 65 | #[macro_export] 66 | macro_rules! opt_data { 67 | ($data:ident) => { 68 | fn data(self) -> Option { 69 | Some(OptionData::$data(self)) 70 | } 71 | }; 72 | 73 | () => { 74 | fn data(self) -> Option { 75 | None 76 | } 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/nsid.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::ToNetworkOrder; 4 | use type2network_derive::ToNetwork; 5 | 6 | use crate::dns::buffer::Buffer; 7 | use crate::{opt_code, opt_data, opt_len}; 8 | 9 | use serde::Serialize; 10 | 11 | use super::{ 12 | opt_rr::{OptionCode, OptionData}, 13 | OptionDataValue, 14 | }; 15 | 16 | // NSID: https://www.rfc-editor.org/rfc/rfc5001.html 17 | #[derive(Debug, Default, ToNetwork, Serialize)] 18 | pub struct NSID(Option); 19 | 20 | impl From for NSID { 21 | fn from(buf: Buffer) -> Self { 22 | Self(Some(buf)) 23 | } 24 | } 25 | 26 | impl fmt::Display for NSID { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | if let Some(b) = &self.0 { 29 | write!(f, "{}", b.display())? 30 | } 31 | 32 | Ok(()) 33 | } 34 | } 35 | 36 | impl OptionDataValue for NSID { 37 | // return the option code for the option data 38 | opt_code!(NSID); 39 | 40 | // return option data length 41 | opt_len!(0); 42 | 43 | // return None 44 | opt_data!(); 45 | } 46 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/padding.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::ToNetworkOrder; 4 | use type2network_derive::ToNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use crate::{dns::buffer::Buffer, opt_code, opt_data}; 9 | 10 | use super::{ 11 | opt_rr::{OptionCode, OptionData}, 12 | OptionDataValue, 13 | }; 14 | 15 | // https://www.rfc-editor.org/rfc/rfc7830.html 16 | #[derive(Debug, Default, ToNetwork, Serialize)] 17 | pub struct Padding(Option); 18 | 19 | impl Padding { 20 | pub fn new(len: u16) -> Self { 21 | if len == 0 { 22 | Self(None) 23 | } else { 24 | let buf = Buffer::with_capacity(len); 25 | //buf.fill(0); 26 | Self(Some(buf)) 27 | } 28 | } 29 | } 30 | 31 | impl From for Padding { 32 | fn from(buf: Buffer) -> Self { 33 | Self(Some(buf)) 34 | } 35 | } 36 | 37 | impl fmt::Display for Padding { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | if self.0.is_some() { 40 | let buf = self.0.as_ref().unwrap(); 41 | write!(f, "{:?}", buf)?; 42 | write!(f, "{}", buf)?; 43 | } 44 | 45 | Ok(()) 46 | } 47 | } 48 | 49 | impl OptionDataValue for Padding { 50 | // return the option code for the option data 51 | opt_code!(Padding); 52 | 53 | // return option data length 54 | fn len(&self) -> u16 { 55 | if self.0.is_none() { 56 | 0 57 | } else { 58 | self.0.as_ref().unwrap().len() as u16 59 | } 60 | } 61 | 62 | // return the option data enum arm 63 | opt_data!(Padding); 64 | } 65 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/report_chanel.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::ToNetworkOrder; 4 | use type2network_derive::ToNetwork; 5 | 6 | use crate::dns::rfc::domain::DomainName; 7 | use crate::{opt_code, opt_data, opt_len}; 8 | 9 | use serde::Serialize; 10 | 11 | use super::{ 12 | opt_rr::{OptionCode, OptionData}, 13 | OptionDataValue, 14 | }; 15 | 16 | // ReportChanel: https://www.rfc-editor.org/rfc/rfc9567.html 17 | #[derive(Debug, Default, ToNetwork, Serialize)] 18 | pub struct ReportChannel(DomainName); 19 | 20 | impl From for ReportChannel { 21 | fn from(dn: DomainName) -> Self { 22 | Self(dn) 23 | } 24 | } 25 | 26 | impl fmt::Display for ReportChannel { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | write!(f, "{}", self.0) 29 | } 30 | } 31 | 32 | impl OptionDataValue for ReportChannel { 33 | // return the option code for the option data 34 | opt_code!(ReportChannel); 35 | 36 | // return option data length 37 | opt_len!(0); 38 | 39 | // return None 40 | opt_data!(); 41 | } 42 | -------------------------------------------------------------------------------- /src/dns/rfc/opt/zoneversion.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::ToNetworkOrder; 4 | use type2network_derive::ToNetwork; 5 | 6 | use crate::dns::buffer::Buffer; 7 | use crate::{opt_code, opt_data, opt_len}; 8 | 9 | use serde::Serialize; 10 | 11 | use super::{ 12 | opt_rr::{OptionCode, OptionData}, 13 | OptionDataValue, 14 | }; 15 | 16 | // ZONEVERSION: https://www.rfc-editor.org/rfc/rfc9660.html 17 | #[derive(Debug, Default, ToNetwork, Serialize)] 18 | pub struct ZV { 19 | pub label_count: u8, 20 | pub r#type: u8, 21 | pub version: Buffer, 22 | } 23 | 24 | #[derive(Debug, Default, ToNetwork, Serialize)] 25 | pub struct ZONEVERSION(pub Option); 26 | 27 | impl ZONEVERSION { 28 | pub fn new() -> Self { 29 | Self(Some(ZV::default())) 30 | } 31 | } 32 | 33 | impl From for ZONEVERSION { 34 | fn from(zv: ZV) -> Self { 35 | Self(Some(zv)) 36 | } 37 | } 38 | 39 | impl fmt::Display for ZONEVERSION { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | if let Some(zv) = &self.0 { 42 | write!(f, "{}", zv.label_count)?; 43 | write!(f, "{}", zv.r#type)?; 44 | write!(f, "{}", zv.version.display())?; 45 | } 46 | 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl OptionDataValue for ZONEVERSION { 52 | // return the option code for the option data 53 | opt_code!(ZONEVERSION); 54 | 55 | // return option data length 56 | opt_len!(0); 57 | 58 | // return None 59 | opt_data!(); 60 | } 61 | -------------------------------------------------------------------------------- /src/dns/rfc/packet_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use byteorder::ReadBytesExt; 4 | use serde::Serialize; 5 | 6 | use type2network::FromNetworkOrder; 7 | use type2network_derive::FromNetwork; 8 | 9 | use enum_from::EnumTryFrom; 10 | 11 | /// The header flags' first bit is 0 or 1 meaning a question or a response. Better is to use an enum which is 12 | /// both clearer and type oriented. 13 | #[derive(Debug, Default, Clone, Copy, PartialEq, EnumTryFrom, FromNetwork, Serialize)] 14 | #[repr(u8)] 15 | #[from_network(TryFrom)] 16 | pub enum PacketType { 17 | #[default] 18 | Query = 0, 19 | Response = 1, 20 | } 21 | 22 | impl fmt::Display for PacketType { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | // output depends on whether it's a query or a response 25 | // because some fields are unnecessary when Query or Response 26 | match *self { 27 | PacketType::Query => write!(f, "QUERY"), 28 | PacketType::Response => write!(f, "RESPONSE"), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/dns/rfc/ptr.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | // CNAME resource record 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct PTR(DomainName); 13 | 14 | impl fmt::Display for PTR { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | write!(f, "{}", self.0) 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use crate::{ 23 | dns::rfc::{rdata::RData, response::Response}, 24 | dns::tests::get_packets, 25 | test_rdata, 26 | }; 27 | 28 | use type2network::FromNetworkOrder; 29 | 30 | use super::PTR; 31 | 32 | test_rdata!( 33 | rdata, 34 | "./tests/pcap/ptr.pcap", 35 | false, 36 | 1, 37 | RData::PTR, 38 | (|x: &PTR, _| { 39 | assert_eq!(&x.to_string(), "ptr.dns.netmeister.org."); 40 | }) 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/dns/rfc/qclass.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | 4 | use enum_from::{EnumDisplay, EnumFromStr, EnumTryFrom}; 5 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 6 | use type2network_derive::{FromNetwork, ToNetwork}; 7 | 8 | // https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.4 9 | #[derive( 10 | Debug, Default, Copy, Clone, PartialEq, EnumFromStr, EnumTryFrom, EnumDisplay, ToNetwork, FromNetwork, Serialize, 11 | )] 12 | #[repr(u16)] 13 | #[from_network(TryFrom)] 14 | pub enum QClass { 15 | #[default] 16 | IN = 1, // the Internet 17 | CS = 2, // the CSNET class (Obsolete - used only for examples in some obsolete RFCs) 18 | CH = 3, // the CHAOS class 19 | HS = 4, // Hesiod [Dyer 87] 20 | ANY = 255, 21 | 22 | #[fallback] 23 | CLASS(u16), 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | 29 | use super::*; 30 | use crate::dns::tests::{from_network_test, to_network_test}; 31 | 32 | #[test] 33 | fn conversion() { 34 | use std::str::FromStr; 35 | 36 | // from_str 37 | let qc = QClass::from_str("IN").unwrap(); 38 | assert_eq!(qc, QClass::IN); 39 | let qc = QClass::from_str("foo").unwrap_err(); 40 | assert_eq!(qc, format!("no variant corresponding to value 'foo'")); 41 | let qc = QClass::from_str("CLASS1234").unwrap(); 42 | assert_eq!(qc, QClass::CLASS(1234)); 43 | let qc = QClass::from_str("CLASSA234").unwrap_err(); 44 | assert_eq!(qc, format!("no variant corresponding to value 'CLASSA234'")); 45 | 46 | // try_from 47 | let qc = QClass::try_from(4u16).unwrap(); 48 | assert_eq!(qc, QClass::HS); 49 | let qc = QClass::try_from(1000u16).unwrap(); 50 | assert_eq!(qc, QClass::CLASS(1000)); 51 | 52 | // display 53 | let qc = QClass::from_str("IN").unwrap(); 54 | assert_eq!(&qc.to_string(), "IN"); 55 | let qc = QClass::try_from(2u16).unwrap(); 56 | assert_eq!(&qc.to_string(), "CS"); 57 | let qc = QClass::try_from(1000u16).unwrap(); 58 | assert_eq!(&qc.to_string(), "CLASS1000"); 59 | let qc = QClass::from_str("CLASS1234").unwrap(); 60 | assert_eq!(&qc.to_string(), "CLASS1234"); 61 | } 62 | 63 | #[test] 64 | fn network() { 65 | let q = QClass::ANY; 66 | to_network_test(&q, 2, &[0x00, 0xFF]); 67 | from_network_test(None, &q, &vec![0x00, 0xFF]); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/dns/rfc/question.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 4 | use type2network_derive::{FromNetwork, ToNetwork}; 5 | 6 | use serde::Serialize; 7 | 8 | use crate::dns::rfc::{domain::DomainName, qclass::QClass, qtype::QType}; 9 | use crate::TITLES; 10 | 11 | // Question structure: https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.2 12 | // 1 1 1 1 1 1 13 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 14 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 15 | // | | 16 | // / QNAME / 17 | // / / 18 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 19 | // | QTYPE | 20 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 21 | // | QCLASS | 22 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 23 | #[derive(Debug, Default, Clone, PartialEq, ToNetwork, FromNetwork, Serialize)] 24 | pub struct Question { 25 | pub qname: DomainName, 26 | pub qtype: QType, 27 | pub qclass: QClass, 28 | } 29 | 30 | impl fmt::Display for Question { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | write!( 33 | f, 34 | "{}:{} {}:{:?} {}:{:?}", 35 | TITLES["qname"], self.qname, TITLES["qtype"], self.qtype, TITLES["qclass"], self.qclass 36 | ) 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 43 | 44 | #[test] 45 | fn network() { 46 | use crate::dns::{rfc::qclass::QClass, rfc::qtype::QType, rfc::question::Question}; 47 | use std::io::Cursor; 48 | 49 | let sample = vec![ 50 | 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 51 | 0x00, 0x01, 52 | ]; 53 | let mut buffer = Cursor::new(sample.as_slice()); 54 | let mut q = Question::default(); 55 | assert!(q.deserialize_from(&mut buffer).is_ok()); 56 | assert_eq!(q.qname.to_string(), "www.google.com."); 57 | assert_eq!(q.qtype, QType::A); 58 | assert_eq!(q.qclass, QClass::IN); 59 | 60 | let mut buffer: Vec = Vec::new(); 61 | assert!(q.serialize_to(&mut buffer).is_ok()); 62 | assert_eq!(buffer, sample); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/dns/rfc/response_code.rs: -------------------------------------------------------------------------------- 1 | use byteorder::ReadBytesExt; 2 | use serde::Serialize; 3 | 4 | use enum_from::{EnumDisplay, EnumTryFrom}; 5 | use type2network::FromNetworkOrder; 6 | use type2network_derive::FromNetwork; 7 | 8 | // response codes: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 9 | #[derive(Debug, Default, Clone, Copy, PartialEq, EnumTryFrom, EnumDisplay, FromNetwork, Serialize)] 10 | #[repr(u8)] 11 | #[from_network(TryFrom)] 12 | pub enum ResponseCode { 13 | #[default] 14 | NoError = 0, // No Error [RFC1035] 15 | FormErr = 1, // Format Error [RFC1035] 16 | ServFail = 2, // Server Failure [RFC1035] 17 | NXDomain = 3, // Non-Existent Domain [RFC1035] 18 | NotImp = 4, // Not Implemented [RFC1035] 19 | Refused = 5, // Query Refused [RFC1035] 20 | YXDomain = 6, // Name Exists when it should not [RFC2136][RFC6672] 21 | YXRRSet = 7, // RR Set Exists when it should not [RFC2136] 22 | NXRRSet = 8, // RR Set that should exist does not [RFC2136] 23 | //NotAuth = 9, // Server Not Authoritative for zone [RFC2136] 24 | NotAuth = 9, // Not Authorized [RFC8945] 25 | NotZone = 10, // Name not contained in zone [RFC2136] 26 | DSOTYPENI = 11, // DSO-TYPE Not Implemented [RFC8490] 27 | // 12-Unassigned = 15, 28 | //BADVERS = 16, // Bad OPT Version [RFC6891] 29 | BADSIG = 16, // TSIG Signature Failure [RFC8945] 30 | BADKEY = 17, // Key not recognized [RFC8945] 31 | BADTIME = 18, // Signature out of time window [RFC8945] 32 | BADMODE = 19, // Bad TKEY Mode [RFC2930] 33 | BADNAME = 20, // Duplicate key name [RFC2930] 34 | BADALG = 21, // Algorithm not supported [RFC2930] 35 | BADTRUNC = 22, // Bad Truncation [RFC8945] 36 | BADCOOKIE = 23, // Bad/missing Server Cookie [RFC7873] 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | #[test] 44 | fn try_from() { 45 | use std::convert::TryFrom; 46 | assert_eq!(ResponseCode::try_from(23).unwrap(), ResponseCode::BADCOOKIE); 47 | assert!(ResponseCode::try_from(110).is_err()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/dns/rfc/rp.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | #[derive(Debug, Default, FromNetwork, Serialize)] 11 | pub struct RP { 12 | mbox: DomainName, 13 | hostname: DomainName, 14 | } 15 | 16 | impl fmt::Display for RP { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | write!(f, "{} {}", self.mbox, self.hostname) 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use crate::{ 25 | dns::rfc::{rdata::RData, response::Response}, 26 | dns::tests::get_packets, 27 | test_rdata, 28 | }; 29 | 30 | use type2network::FromNetworkOrder; 31 | 32 | use super::RP; 33 | 34 | test_rdata!( 35 | rdata, 36 | "./tests/pcap/rp.pcap", 37 | false, 38 | 1, 39 | RData::RP, 40 | (|x: &RP, _| { 41 | assert_eq!(&x.to_string(), "jschauma.netmeister.org. contact.netmeister.org."); 42 | }) 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/dns/rfc/rrlist.rs: -------------------------------------------------------------------------------- 1 | //! RRSet is a list of resource records for the same domain name. 2 | //! 3 | use std::{fmt, net::IpAddr, ops::Deref}; 4 | 5 | #[allow(unused_imports)] 6 | use rand::seq::IteratorRandom; 7 | use serde::Serialize; 8 | 9 | use type2network::{FromNetworkOrder, ToNetworkOrder}; 10 | use type2network_derive::{FromNetwork, ToNetwork}; 11 | 12 | use super::{domain::DomainName, qtype::QType, resource_record::ResourceRecord}; 13 | // use crate::show::{DisplayOptions, Show}; 14 | 15 | #[derive(Debug, Default, FromNetwork, ToNetwork, Serialize)] 16 | pub struct RRList(Vec); 17 | 18 | impl RRList { 19 | // necessary for deserialization 20 | pub fn with_capacity(capa: usize) -> Self { 21 | Self(Vec::with_capacity(capa)) 22 | } 23 | 24 | // in case a RR in the set is a A or AAAA type, return the corresponding ip address 25 | pub fn ip_address>(&self, qt: &QType, name: T) -> Option { 26 | let name = name.try_into().ok()?; 27 | 28 | let rr = self.0.iter().filter(|x| x.name == name && x.r#type == *qt).nth(0); 29 | 30 | if let Some(rr) = rr { 31 | rr.ip_address() 32 | } else { 33 | None 34 | } 35 | } 36 | 37 | // return a random RR corresponding to the QType 38 | pub fn random(&self, qt: &QType) -> Option<&ResourceRecord> { 39 | let mut rng = rand::thread_rng(); 40 | 41 | self.0.iter().filter(|rr| rr.r#type == *qt).choose(&mut rng) 42 | } 43 | 44 | // return the maximum length of all domain names in all RRs in the RR set 45 | // used to align all domain names in output 46 | pub fn max_length(&self) -> Option { 47 | self.0.iter().map(|x| x.name.len()).max() 48 | } 49 | 50 | // same as before but when displaying UTF-8 IDNA 51 | pub fn max_count(&self) -> Option { 52 | self.0.iter().map(|x| x.name.count()).max() 53 | } 54 | 55 | // pub fn foo

(&self, dimension: P) -> Option 56 | // where 57 | // P: Fn(&ResourceRecord) -> usize, 58 | // { 59 | // self.0.iter().map(|x| dimension(x)).max() 60 | // } 61 | } 62 | 63 | impl Deref for RRList { 64 | type Target = Vec; 65 | 66 | fn deref(&self) -> &Self::Target { 67 | &self.0 68 | } 69 | } 70 | 71 | impl fmt::Display for RRList { 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 73 | for rr in &self.0 { 74 | writeln!(f, "{}", rr)?; 75 | } 76 | 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | 84 | use crate::{ 85 | dns::{ 86 | rfc::{domain::DomainName, qtype::QType, response::Response}, 87 | tests::get_packets, 88 | }, 89 | error::{Dns, Error}, 90 | }; 91 | use type2network::FromNetworkOrder; 92 | 93 | #[test] 94 | fn cap4() -> crate::error::Result<()> { 95 | let pcap = get_packets("./tests/pcap/cap4.pcap", 0, 1); 96 | let mut buffer = std::io::Cursor::new(&pcap.1[0x2A..]); 97 | 98 | let mut resp = Response::default(); 99 | resp.deserialize_from(&mut buffer) 100 | .map_err(|_| Error::Dns(Dns::CantDeserialize))?; 101 | 102 | // no anwser is response => this is a referral 103 | assert!(resp.is_referral()); 104 | 105 | assert!(resp.authority().is_some()); 106 | let auth = resp.authority().as_ref().unwrap(); 107 | assert_eq!(auth.len(), 13); 108 | 109 | assert!(resp.additional().is_some()); 110 | let add = resp.additional().as_ref().unwrap(); 111 | assert_eq!(add.len(), 27); 112 | 113 | let ip = add.ip_address(&QType::A, "l.gtld-servers.net.").unwrap(); 114 | assert_eq!(ip.to_string(), "192.41.162.30"); 115 | let ip = add.ip_address(&QType::AAAA, "g.gtld-servers.net.").unwrap(); 116 | assert_eq!(ip.to_string(), "2001:503:eea3::30"); 117 | let ip = add.ip_address(&QType::AAAA, "foo."); 118 | assert!(ip.is_none()); 119 | 120 | let d = DomainName::try_from("i.gtld-servers.net.").unwrap(); 121 | let ip = add.ip_address(&QType::A, d).unwrap(); 122 | assert_eq!(ip.to_string(), "192.43.172.30"); 123 | 124 | // let answer = &answer[0]; 125 | // assert_eq!(format!("{}", answer.name), "www.google.com."); 126 | // assert_eq!(answer.r#type, QType::A); 127 | // assert!(matches!(&answer.opt_or_else, OptOrElse::Regular(x) if x.class == QClass::IN)); 128 | // assert!(matches!(&answer.opt_or_else, OptOrElse::Regular(x) if x.ttl == 119)); 129 | // assert_eq!(answer.rd_length, 4); 130 | 131 | // assert!( 132 | // matches!(answer.r_data, RData::A(A(addr)) if Ipv4Addr::from(addr) == Ipv4Addr::new(172,217,18,36)) 133 | // ); 134 | 135 | Ok(()) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/dns/rfc/soa.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::domain::DomainName; 9 | 10 | // SOA RR 11 | #[derive(Debug, Default, PartialEq, FromNetwork, Serialize)] 12 | pub struct SOA { 13 | pub mname: DomainName, // The of the name server that was the 14 | // original or primary source of data for this zone. 15 | pub rname: DomainName, // A which specifies the mailbox of the 16 | // person responsible for this zone. 17 | pub serial: u32, // The unsigned 32 bit version number of the original copy 18 | // of the zone. Zone transfers preserve this value. This 19 | // value wraps and should be compared using sequence space 20 | // arithmetic. 21 | pub refresh: u32, // A 32 bit time interval before the zone should be 22 | // refreshed. 23 | pub retry: u32, // A 32 bit time interval that should elapse before a 24 | // failed refresh should be retried. 25 | pub expire: u32, // A 32 bit time value that specifies the upper limit on 26 | // the time interval that can elapse before the zone is no 27 | // longer authoritative. 28 | pub minimum: u32, //The unsigned 32 bit minimum TTL field that should be 29 | //exported with any RR from this zone. 30 | } 31 | 32 | impl fmt::Display for SOA { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!( 35 | f, 36 | "{} {} {} {} {} {} {}", 37 | self.mname, self.rname, self.serial, self.refresh, self.retry, self.expire, self.minimum 38 | ) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use crate::{ 45 | dns::rfc::{rdata::RData, response::Response}, 46 | dns::tests::get_packets, 47 | test_rdata, 48 | }; 49 | 50 | use type2network::FromNetworkOrder; 51 | 52 | use super::SOA; 53 | 54 | test_rdata!( 55 | rdata, 56 | "./tests/pcap/soa.pcap", 57 | false, 58 | 1, 59 | RData::SOA, 60 | (|x: &SOA, _| { 61 | assert_eq!( 62 | &x.to_string(), 63 | "panix.netmeister.org. jschauma.netmeister.org. 2021072599 3600 300 3600000 3600" 64 | ); 65 | }) 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/dns/rfc/srv.rs: -------------------------------------------------------------------------------- 1 | //! Definition of the SRV record (https://datatracker.ietf.org/doc/html/rfc2782) 2 | use std::fmt; 3 | 4 | // use log::trace; 5 | use type2network::FromNetworkOrder; 6 | use type2network_derive::FromNetwork; 7 | 8 | use serde::Serialize; 9 | 10 | use super::domain::DomainName; 11 | 12 | // https://datatracker.ietf.org/doc/html/rfc2782 13 | #[derive(Debug, Default, FromNetwork, Serialize)] 14 | pub struct SRV { 15 | priority: u16, 16 | weight: u16, 17 | port: u16, 18 | target: DomainName, 19 | } 20 | 21 | impl fmt::Display for SRV { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | write!(f, "{} {} {} {}", self.priority, self.weight, self.port, self.target)?; 24 | 25 | Ok(()) 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use crate::{ 32 | dns::rfc::{rdata::RData, response::Response}, 33 | dns::tests::get_packets, 34 | test_rdata, 35 | }; 36 | 37 | use type2network::FromNetworkOrder; 38 | 39 | use super::SRV; 40 | 41 | test_rdata!( 42 | rdata, 43 | "./tests/pcap/srv.pcap", 44 | false, 45 | 1, 46 | RData::SRV, 47 | (|x: &SRV, _| { 48 | assert_eq!(&x.to_string(), "0 1 80 panix.netmeister.org."); 49 | }) 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/dns/rfc/sshfp.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // use log::trace; 4 | use type2network::FromNetworkOrder; 5 | use type2network_derive::FromNetwork; 6 | 7 | use crate::{dns::buffer::Buffer, new_rd_length}; 8 | 9 | // https://datatracker.ietf.org/doc/html/rfc4255#section-3 10 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 11 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | // | algorithm | fp type | / 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / 15 | // / / 16 | // / fingerprint / 17 | // / / 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | #[derive(Debug, Default, FromNetwork)] 20 | pub struct SSHFP { 21 | // transmistted through RR deserialization 22 | #[from_network(ignore)] 23 | pub(super) rd_length: u16, 24 | 25 | algorithm: u8, 26 | fp_type: u8, 27 | 28 | #[from_network(with_code( self.fingerprint = Buffer::with_capacity(self.rd_length - 2); ))] 29 | fingerprint: Buffer, 30 | } 31 | 32 | // auto-implement new 33 | new_rd_length!(SSHFP); 34 | 35 | impl fmt::Display for SSHFP { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | write!(f, "{} {} {:?}", self.algorithm, self.fp_type, self.fingerprint) 38 | } 39 | } 40 | 41 | // Custom serialization 42 | use serde::{ser::SerializeMap, Serialize, Serializer}; 43 | impl Serialize for SSHFP { 44 | fn serialize(&self, serializer: S) -> std::result::Result 45 | where 46 | S: Serializer, 47 | { 48 | let mut seq = serializer.serialize_map(Some(3))?; 49 | seq.serialize_entry("algorithm", &self.algorithm)?; 50 | seq.serialize_entry("fp_type", &self.fp_type)?; 51 | seq.serialize_entry("fingerprint", &self.fingerprint.to_string())?; 52 | seq.end() 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use crate::{ 59 | dns::rfc::{rdata::RData, response::Response}, 60 | dns::tests::get_packets, 61 | test_rdata, 62 | }; 63 | 64 | use type2network::FromNetworkOrder; 65 | 66 | use super::SSHFP; 67 | 68 | test_rdata!( 69 | rdata, 70 | "./tests/pcap/sshfp.pcap", 71 | false, 72 | 1, 73 | RData::SSHFP, 74 | (|x: &SSHFP, i: usize| { 75 | match i { 76 | 0 => assert_eq!(x.to_string(), "1 1 53A76D5284C91E14DEC9AD1A757DA123B95B081"), 77 | 1 => assert_eq!( 78 | x.to_string(), 79 | "3 2 62475A22F1E4F09594206539AAFF90A6EDAABAB1BA6F4A67AB396177455CF84" 80 | ), 81 | _ => panic!("data not is the pcap file"), 82 | } 83 | }) 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/dns/rfc/tkey.rs: -------------------------------------------------------------------------------- 1 | // use std::fmt; 2 | 3 | use std::fmt; 4 | 5 | use type2network::FromNetworkOrder; 6 | use type2network_derive::FromNetwork; 7 | 8 | use crate::{dns::buffer::Buffer, new_rd_length}; 9 | 10 | use super::domain::DomainName; 11 | 12 | // https://www.rfc-editor.org/rfc/rfc2930#section-2 13 | // Algorithm: domain 14 | // Inception: u_int32_t 15 | // Expiration: u_int32_t 16 | // Mode: u_int16_t 17 | // Error: u_int16_t 18 | // Key Size: u_int16_t 19 | // Key Data: octet-stream 20 | // Other Size: u_int16_t 21 | // Other Data: octet-stream undefined by this specification 22 | #[derive(Debug, Default, FromNetwork)] 23 | pub struct TKEY { 24 | #[from_network(ignore)] 25 | pub(super) rd_length: u16, 26 | 27 | algorithm: DomainName, 28 | inception: u32, 29 | expiration: u32, 30 | mode: u16, 31 | error: u16, 32 | 33 | key_size: u16, 34 | #[from_network(with_code( self.key_data = Buffer::with_capacity(self.key_size); ))] 35 | key_data: Buffer, 36 | 37 | other_size: u16, 38 | #[from_network(with_code( self.key_data = Buffer::with_capacity(self.other_size); ))] 39 | other_data: Buffer, 40 | } 41 | 42 | // auto-implement new 43 | new_rd_length!(TKEY); 44 | 45 | impl fmt::Display for TKEY { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | write!( 48 | f, 49 | "{} {} {} {} {} {} {} {} {}", 50 | self.algorithm, 51 | self.inception, 52 | self.expiration, 53 | self.mode, 54 | self.error, 55 | self.key_size, 56 | self.key_data, 57 | self.other_size, 58 | self.other_data 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/dns/rfc/tlsa.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use crate::{dns::buffer::Buffer, new_rd_length}; 7 | 8 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 9 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | // | Cert. Usage | Selector | Matching Type | / 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / 13 | // / / 14 | // / Certificate Association Data / 15 | // / / 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | #[allow(clippy::upper_case_acronyms)] 18 | #[derive(Debug, Default, FromNetwork)] 19 | pub struct TLSA { 20 | #[from_network(ignore)] 21 | rd_length: u16, 22 | 23 | cert_usage: u8, 24 | selector: u8, 25 | matching_type: u8, 26 | 27 | #[from_network(with_code( self.data = Buffer::with_capacity(self.rd_length - 3); ))] 28 | data: Buffer, 29 | } 30 | 31 | // auto-implement new 32 | new_rd_length!(TLSA); 33 | 34 | impl fmt::Display for TLSA { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!( 37 | f, 38 | "{} {} {} {:?}", 39 | self.cert_usage, self.selector, self.matching_type, self.data 40 | ) 41 | } 42 | } 43 | 44 | // Custom serialization 45 | use serde::{ser::SerializeMap, Serialize, Serializer}; 46 | impl Serialize for TLSA { 47 | fn serialize(&self, serializer: S) -> std::result::Result 48 | where 49 | S: Serializer, 50 | { 51 | let mut seq = serializer.serialize_map(Some(4))?; 52 | seq.serialize_entry("cert_usage", &self.cert_usage)?; 53 | seq.serialize_entry("selector", &self.selector)?; 54 | seq.serialize_entry("matching_type", &self.matching_type)?; 55 | seq.serialize_entry("data", &self.data.to_string())?; 56 | seq.end() 57 | } 58 | } 59 | 60 | // https://datatracker.ietf.org/doc/html/rfc8162 61 | #[allow(clippy::upper_case_acronyms)] 62 | pub(super) type SMIMEA = TLSA; 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use crate::{ 67 | dns::rfc::{rdata::RData, response::Response}, 68 | dns::tests::get_packets, 69 | test_rdata, 70 | }; 71 | 72 | use type2network::FromNetworkOrder; 73 | 74 | use super::{SMIMEA, TLSA}; 75 | 76 | test_rdata!( 77 | rdata_tlsa, 78 | "./tests/pcap/tlsa.pcap", 79 | false, 80 | 1, 81 | RData::TLSA, 82 | (|x: &TLSA, _| { 83 | assert_eq!( 84 | &x.to_string(), 85 | "3 1 1 8CE14CBE1FAFAE9FB25845D335E0E416BC2FAE02E8746689C06DA59C1F9382" 86 | ); 87 | }) 88 | ); 89 | 90 | test_rdata!( 91 | rdata_smimea, 92 | "./tests/pcap/smimea.pcap", 93 | false, 94 | 1, 95 | RData::SMIMEA, 96 | (|x: &SMIMEA, _| { 97 | assert_eq!( 98 | &x.to_string(), 99 | "3 1 1 8CE14CBE1FAFAE9FB25845D335E0E416BC2FAE02E8746689C06DA59C1F9382" 100 | ); 101 | }) 102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /src/dns/rfc/txt.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::char_string::CharacterString; 9 | 10 | // MX RR 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct TXT(pub Vec); 13 | 14 | impl fmt::Display for TXT { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | for cs in &self.0 { 17 | write!(f, "{}", cs)?; 18 | } 19 | 20 | Ok(()) 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use crate::{ 27 | dns::rfc::{rdata::RData, response::Response}, 28 | dns::tests::get_packets, 29 | test_rdata, 30 | }; 31 | 32 | use type2network::FromNetworkOrder; 33 | 34 | use super::TXT; 35 | 36 | test_rdata!( 37 | rdata, 38 | "./tests/pcap/txt.pcap", 39 | false, 40 | 1, 41 | RData::TXT, 42 | (|x: &TXT, i: usize| { 43 | match i { 44 | 0 => assert_eq!( 45 | x.to_string(), 46 | "Descriptive text. Completely overloaded for all sorts of things. RFC1035 (1987)" 47 | ), 48 | 1 => assert_eq!(x.to_string(), "Format: "), 49 | _ => panic!("data not is the pcap file"), 50 | } 51 | }) 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/dns/rfc/uri.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use crate::{dns::buffer::Buffer, new_rd_length}; 7 | 8 | // https://datatracker.ietf.org/doc/html/rfc7553 9 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 10 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 11 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | // | Priority | Weight | 13 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | // / / 15 | // / Target / 16 | // / / 17 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | #[allow(clippy::upper_case_acronyms)] 19 | #[derive(Debug, Default, FromNetwork)] 20 | pub struct URI { 21 | #[from_network(ignore)] 22 | rd_length: u16, 23 | 24 | priority: u16, 25 | weight: u16, 26 | 27 | #[from_network(with_code( self.target = Buffer::with_capacity(self.rd_length - 4); ))] 28 | target: Buffer, 29 | } 30 | 31 | // auto-implement new 32 | new_rd_length!(URI); 33 | 34 | impl fmt::Display for URI { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!( 37 | f, 38 | "{} {} \"{}\"", 39 | self.priority, 40 | self.weight, 41 | String::from_utf8_lossy(&self.target) 42 | ) 43 | } 44 | } 45 | 46 | // Custom serialization 47 | use serde::{ser::SerializeMap, Serialize, Serializer}; 48 | impl Serialize for URI { 49 | fn serialize(&self, serializer: S) -> std::result::Result 50 | where 51 | S: Serializer, 52 | { 53 | let mut seq = serializer.serialize_map(Some(3))?; 54 | seq.serialize_entry("priority", &self.priority)?; 55 | seq.serialize_entry("weight", &self.weight)?; 56 | seq.serialize_entry("target", &String::from_utf8_lossy(&self.target))?; 57 | seq.end() 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use crate::{ 64 | dns::rfc::{rdata::RData, response::Response}, 65 | dns::tests::get_packets, 66 | test_rdata, 67 | }; 68 | 69 | use type2network::FromNetworkOrder; 70 | 71 | use super::URI; 72 | 73 | test_rdata!( 74 | rdata, 75 | "./tests/pcap/uri.pcap", 76 | false, 77 | 1, 78 | RData::URI, 79 | (|x: &URI, _| { 80 | assert_eq!(&x.to_string(), "10 1 \"https://www.netmeister.org/blog/dns-rrs.html\""); 81 | }) 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /src/dns/rfc/wallet.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use serde::Serialize; 7 | 8 | use super::char_string::CharacterString; 9 | 10 | // MX RR 11 | #[derive(Debug, Default, FromNetwork, Serialize)] 12 | pub struct WALLET { 13 | pub abbrev: CharacterString, 14 | pub address: CharacterString, 15 | } 16 | 17 | impl fmt::Display for WALLET { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | write!(f, "{} {}", self.abbrev, self.address) 20 | } 21 | } 22 | 23 | // #[cfg(test)] 24 | // mod tests { 25 | // use crate::{ 26 | // dns::rfc::{rdata::RData, response::Response}, 27 | // dns::tests::get_packets, 28 | // test_rdata, 29 | // }; 30 | 31 | // use type2network::FromNetworkOrder; 32 | 33 | // use super::WALLET; 34 | 35 | // test_rdata!( 36 | // rdata, 37 | // "./tests/pcap/txt.pcap", 38 | // false, 39 | // 1, 40 | // RData::TXT, 41 | // (|x: &TXT, i: usize| { 42 | // match i { 43 | // 0 => assert_eq!( 44 | // x.to_string(), 45 | // "Descriptive text. Completely overloaded for all sorts of things. RFC1035 (1987)" 46 | // ), 47 | // 1 => assert_eq!(x.to_string(), "Format: "), 48 | // _ => panic!("data not is the pcap file"), 49 | // } 50 | // }) 51 | // ); 52 | // } 53 | -------------------------------------------------------------------------------- /src/dns/rfc/zonemd.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use type2network::FromNetworkOrder; 4 | use type2network_derive::FromNetwork; 5 | 6 | use crate::dns::buffer::Buffer; 7 | use crate::new_rd_length; 8 | 9 | // https://www.rfc-editor.org/rfc/rfc8976 10 | // 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 11 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | // | Serial | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // | Scheme |Hash Algorithm | | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 17 | // | Digest | 18 | // / / 19 | // / / 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | #[allow(clippy::upper_case_acronyms)] 22 | #[derive(Debug, Default, FromNetwork)] 23 | pub struct ZONEMD { 24 | #[from_network(ignore)] 25 | rd_length: u16, 26 | 27 | serial: u32, 28 | scheme: u8, 29 | hash_algorithm: u8, 30 | 31 | #[from_network(with_code( self.digest = Buffer::with_capacity(self.rd_length - 6); ))] 32 | digest: Buffer, 33 | } 34 | 35 | // auto-implement new 36 | new_rd_length!(ZONEMD); 37 | 38 | impl fmt::Display for ZONEMD { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | write!( 41 | f, 42 | "{} {} {} {:?}", 43 | self.serial, self.scheme, self.hash_algorithm, self.digest 44 | ) 45 | } 46 | } 47 | 48 | // Custom serialization 49 | use serde::{ser::SerializeMap, Serialize, Serializer}; 50 | impl Serialize for ZONEMD { 51 | fn serialize(&self, serializer: S) -> std::result::Result 52 | where 53 | S: Serializer, 54 | { 55 | let mut seq = serializer.serialize_map(Some(4))?; 56 | seq.serialize_entry("serial", &self.serial)?; 57 | seq.serialize_entry("scheme", &self.scheme)?; 58 | seq.serialize_entry("hash_algorithm", &self.hash_algorithm)?; 59 | seq.serialize_entry("digest", &self.digest.to_string())?; 60 | seq.end() 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use crate::{ 67 | dns::rfc::{rdata::RData, response::Response}, 68 | dns::tests::get_packets, 69 | test_rdata, 70 | }; 71 | 72 | use type2network::FromNetworkOrder; 73 | 74 | use super::ZONEMD; 75 | 76 | test_rdata!( 77 | rdata, 78 | "./tests/pcap/zonemd.pcap", 79 | false, 80 | 1, 81 | RData::ZONEMD, 82 | (|x: &ZONEMD, _| { 83 | assert_eq!(&x.to_string(), "2021071219 1 1 4274F6BC562CF8CE512B21AAA4CCC1EB9F4FAAAECD01642D0A07BDEA890C8845849D615CC590F54BAC7E87B9E41ED"); 84 | }) 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/handlebars.rs: -------------------------------------------------------------------------------- 1 | // Manage handlebars display 2 | use handlebars::*; 3 | use serde::Serialize; 4 | 5 | use crate::QueryInfo; 6 | use dnslib::dns::message::MessageList; 7 | 8 | // custom helper 9 | handlebars_helper!(ljust: |length: usize, x: String| format!("{: { 13 | messages: &'a MessageList, 14 | info: &'a QueryInfo, 15 | } 16 | 17 | impl HelperDef for HBData<'_> { 18 | fn call<'reg: 'rc, 'rc>( 19 | &self, 20 | h: &Helper, 21 | _: &Handlebars, 22 | _: &Context, 23 | _rc: &mut RenderContext, 24 | out: &mut dyn Output, 25 | ) -> HelperResult { 26 | let param1 = h.param(0).unwrap(); 27 | let param2 = h.param(1).unwrap(); 28 | 29 | let length = param1.value().as_u64().unwrap() as usize; 30 | 31 | out.write(&format!("{:; 11 | 12 | pub static TITLES: LazyLock = LazyLock::new(|| { 13 | const COLOR: Color = Color::BrightCyan; 14 | 15 | // local helper 16 | fn insert_title(h: &mut ColoredTitles, title: &str, color: Color) { 17 | h.insert(title.to_string(), title.color(color)); 18 | } 19 | 20 | // init new hmap 21 | let mut h = HashMap::new(); 22 | 23 | // add all titles 24 | insert_title(&mut h, "qname", COLOR); 25 | insert_title(&mut h, "qtype", COLOR); 26 | insert_title(&mut h, "qclass", COLOR); 27 | insert_title(&mut h, "name", COLOR); 28 | insert_title(&mut h, "type", COLOR); 29 | insert_title(&mut h, "payload", COLOR); 30 | insert_title(&mut h, "rcode", COLOR); 31 | insert_title(&mut h, "version", COLOR); 32 | insert_title(&mut h, "flags", COLOR); 33 | 34 | h 35 | }); 36 | 37 | pub fn header_section(text: &str, length: Option) -> ColoredString { 38 | let s = if let Some(l) = length { 39 | format!("{:(messages: T, info: U, lua_code: &str) -> crate::error::Result<()> { 9 | // get lua context 10 | let lua = Lua::new(); 11 | 12 | // convert data to value 13 | let lua_messages = lua.to_value(&messages)?; 14 | let lua_info = lua.to_value(&info)?; 15 | 16 | // we'll set a global name for all the DNS messages and info as well 17 | let globals = lua.globals(); 18 | globals.set("dns", lua_messages)?; 19 | globals.set("info", lua_info)?; 20 | 21 | // execute code 22 | lua.load(lua_code).exec()?; 23 | 24 | Ok(()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/query_info.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::{fmt, net::SocketAddr}; 3 | 4 | //─────────────────────────────────────────────────────────────────────────────────── 5 | // Gather some information which might be useful for the user 6 | //─────────────────────────────────────────────────────────────────────────────────── 7 | #[derive(Debug, Default, Serialize)] 8 | pub struct QueryInfo { 9 | //resolver reached 10 | pub server: Option, 11 | 12 | // elapsed time in ms 13 | pub elapsed: u128, 14 | 15 | // transport used (ex: Udp) 16 | pub mode: String, 17 | 18 | // bytes sent and received during network operations 19 | pub bytes_sent: usize, 20 | pub bytes_received: usize, 21 | } 22 | 23 | impl fmt::Display for QueryInfo { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | if let Some(peer) = self.server { 26 | write!(f, "\nendpoint: {} ({})\n", peer, self.mode)?; 27 | } 28 | writeln!(f, "elapsed: {} ms", self.elapsed)?; 29 | write!( 30 | f, 31 | "sent:{}, received:{} bytes", 32 | self.bytes_sent, self.bytes_received 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/templating.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use tera::*; 3 | 4 | use crate::dns::message::MessageList; 5 | use crate::QueryInfo; 6 | 7 | #[derive(Serialize)] 8 | struct HBData<'a> { 9 | messages: &'a MessageList, 10 | info: &'a QueryInfo, 11 | } 12 | 13 | pub fn render(messages: &MessageList, info: &QueryInfo, tpl: &str) { 14 | let mut context = Context::new(); 15 | 16 | context.insert("messages", messages); 17 | context.insert("info", info); 18 | let rendered = Tera::one_off(tpl, &context, true); 19 | 20 | println!("{}", rendered.unwrap()); 21 | } 22 | -------------------------------------------------------------------------------- /src/trace.rs: -------------------------------------------------------------------------------- 1 | use log::trace; 2 | 3 | use dnslib::dns::rfc::domain::ROOT; 4 | use dnslib::dns::rfc::{domain::ROOT_DOMAIN, qtype::QType}; 5 | use dnslib::error::*; 6 | use dnslib::transport::{endpoint::EndPoint, root_servers::get_root_server}; 7 | 8 | use crate::args::CliOptions; 9 | use crate::get_messages; 10 | use crate::show::Show; 11 | 12 | pub fn trace_resolution(options: &mut CliOptions) -> dnslib::error::Result<()> { 13 | trace!("tracing started"); 14 | 15 | // save original options 16 | let orig_qt = options.protocol.qtype[0]; 17 | let orig_domain = options.protocol.domain_name.clone(); 18 | let orig_ep = options.transport.endpoint.clone(); 19 | 20 | // no recursion wanted 21 | options.flags.recursion_desired = true; 22 | 23 | // send NS . to my DNS to get list of root servers 24 | trace!("query:{} domain:{} server:{}", QType::NS, ROOT, orig_ep); 25 | options.protocol.qtype = vec![QType::NS]; 26 | options.protocol.domain_name = ROOT_DOMAIN; 27 | let messages = get_messages(None, options)?; 28 | let resp = messages[0].response(); 29 | resp.show(&options.display, None); 30 | println!(); 31 | 32 | // chose a random root server 33 | let mut ip = get_root_server(&options.transport.ip_version, None); 34 | options.protocol.qtype = vec![orig_qt]; 35 | 36 | // reset the original domain to query 37 | options.protocol.domain_name = orig_domain.clone(); 38 | 39 | loop { 40 | // iterative query => RD = false 41 | options.flags.recursion_desired = false; 42 | 43 | options.transport.endpoint = EndPoint::try_from((&ip, options.transport.port))?; 44 | trace!( 45 | "query:{} domain:{} server:{}", 46 | orig_qt, 47 | orig_domain, 48 | options.transport.endpoint 49 | ); 50 | 51 | let messages = get_messages(None, options)?; 52 | let resp = messages[0].response(); 53 | resp.show(&options.display, None); 54 | println!(); 55 | 56 | // did we find the ip address for the domain we asked for ? 57 | if let Some(_ip) = resp.ip_address(&orig_qt, &options.protocol.domain_name) { 58 | // println!("!!! found ip={}", ip); 59 | return Ok(()); 60 | } 61 | 62 | // no, so continue. If glue records, this means we have addresses 63 | if let Some(rr) = resp.random_glue_record(&orig_qt) { 64 | ip = rr.ip_address().ok_or(Error::Dns(Dns::ImpossibleToTrace))?; 65 | } else { 66 | // query regular resolver for resolving random ns server in the auth section 67 | let rr = resp.random_ns_record().ok_or(Error::Dns(Dns::ImpossibleToTrace))?; 68 | 69 | options.flags.recursion_desired = true; 70 | 71 | options.transport.endpoint = orig_ep.clone(); 72 | options.protocol.domain_name = rr.ns_name().ok_or(Error::Dns(Dns::ImpossibleToTrace))?; 73 | 74 | trace!( 75 | "query:{} domain:{} server:{}", 76 | orig_qt, 77 | orig_domain, 78 | options.transport.endpoint 79 | ); 80 | let messages = get_messages(None, options)?; 81 | let resp = messages[0].response(); 82 | resp.show(&options.display, None); 83 | 84 | // find the ip address 85 | ip = resp 86 | .ip_address(&orig_qt, &options.protocol.domain_name) 87 | .ok_or(Error::Dns(Dns::ImpossibleToTrace))?; 88 | 89 | // reset to the original domain we're looking for 90 | options.protocol.domain_name = orig_domain.clone(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/transport/crypto.rs: -------------------------------------------------------------------------------- 1 | // Common functions for TLS related processes (TLS, QUIC) 2 | 3 | use rustls::{ClientConfig, RootCertStore}; 4 | use rustls_pki_types::CertificateDer; 5 | 6 | use crate::error::{Error, Result}; 7 | 8 | // build a new client config for TLS connexions 9 | pub fn tls_config(root_store: RootCertStore) -> ClientConfig { 10 | ClientConfig::builder() 11 | .with_root_certificates(root_store) 12 | .with_no_client_auth() 13 | } 14 | 15 | // manage CAs 16 | pub fn root_store(cert: &Option>) -> Result { 17 | let mut root_store = rustls::RootCertStore::empty(); 18 | 19 | // we've got a certificate here 20 | if let Some(buf) = cert { 21 | let cert = CertificateDer::from_slice(buf); 22 | root_store.add(cert).map_err(Error::Tls)?; 23 | } 24 | // use root CAs 25 | else { 26 | root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); 27 | } 28 | 29 | Ok(root_store) 30 | } 31 | -------------------------------------------------------------------------------- /src/transport/network.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | net::{Ipv4Addr, Ipv6Addr, SocketAddr}, 4 | }; 5 | 6 | use crate::error; 7 | 8 | use super::NetworkInfo; 9 | 10 | #[derive(Debug, Default, Clone, PartialEq)] 11 | pub enum IPVersion { 12 | #[default] 13 | Any, 14 | V4, 15 | V6, 16 | } 17 | 18 | impl IPVersion { 19 | // Bind to a socket either to IPV4, IPV6 or any of these 2 20 | // the bind() method will chose the first one which succeeds if IPVersion::Any is passed 21 | pub fn unspecified_ip_vec(&self) -> Vec { 22 | match self { 23 | IPVersion::Any => vec![ 24 | SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)), 25 | SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)), 26 | ], 27 | IPVersion::V4 => vec![SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0))], 28 | IPVersion::V6 => vec![SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0))], 29 | } 30 | } 31 | 32 | // Return 0.0.0.0:0 or [:]:0 depending on IP version 33 | pub fn unspecified_ip(&self) -> SocketAddr { 34 | match self { 35 | IPVersion::Any => SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)), 36 | IPVersion::V4 => SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)), 37 | IPVersion::V6 => SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)), 38 | } 39 | } 40 | } 41 | 42 | // Bind to a socket either to IPV4, IPV6 or any of these 2 43 | // the bind() method will chose the first one which succeeds if IPVersion::Any is passed 44 | // pub fn unspecified_ip(ver: &IPVersion) -> Vec { 45 | // match ver { 46 | // IPVersion::Any => vec![ 47 | // SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)), 48 | // SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)), 49 | // ], 50 | // IPVersion::V4 => vec![SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0))], 51 | // IPVersion::V6 => vec![SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0))], 52 | // } 53 | // } 54 | 55 | #[derive(Debug, Default, Clone, PartialEq)] 56 | pub enum Protocol { 57 | #[default] 58 | Udp, 59 | Tcp, 60 | DoH, 61 | DoT, 62 | DoQ, 63 | } 64 | 65 | impl Protocol { 66 | // default port number for transport or port 67 | pub const fn default_port(&self) -> u16 { 68 | match self { 69 | Protocol::Udp => 53, 70 | Protocol::Tcp => 53, 71 | Protocol::DoT => 853, 72 | Protocol::DoH => 443, 73 | Protocol::DoQ => 853, 74 | } 75 | } 76 | 77 | // true if message needs to be sent with prepended length 78 | pub fn uses_leading_length(&self) -> bool { 79 | *self == Protocol::Tcp || *self == Protocol::DoT || *self == Protocol::DoQ 80 | } 81 | } 82 | 83 | impl fmt::Display for Protocol { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | match self { 86 | Protocol::Udp => write!(f, "Udp"), 87 | Protocol::Tcp => write!(f, "Tcp"), 88 | Protocol::DoT => write!(f, "DoT"), 89 | Protocol::DoH => write!(f, "DoH"), 90 | Protocol::DoQ => write!(f, "DoQ"), 91 | } 92 | } 93 | } 94 | 95 | #[allow(async_fn_in_trait)] 96 | pub trait Messenger { 97 | // send query using the underlying transport 98 | fn send(&mut self, buffer: &[u8]) -> error::Result; 99 | 100 | // async version 101 | async fn asend(&mut self, buffer: &[u8]) -> error::Result; 102 | 103 | // receive response using the underlying transport 104 | fn recv(&mut self, buffer: &mut [u8]) -> error::Result; 105 | 106 | // async version 107 | async fn arecv(&mut self, buffer: &mut [u8]) -> error::Result; 108 | 109 | // async connect used by QUIC 110 | async fn aconnect(&mut self) -> error::Result<()>; 111 | 112 | // true if transporter uses Tcp. This is required for TCP transport to have 2 bytes 113 | // for the message length prepended in the query 114 | fn uses_leading_length(&self) -> bool; 115 | 116 | // return the transport mode (udp, tcp, etc) 117 | fn mode(&self) -> Protocol; 118 | 119 | fn network_info(&self) -> &NetworkInfo; 120 | } 121 | -------------------------------------------------------------------------------- /src/transport/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, net::TcpStream}; 2 | 3 | use log::debug; 4 | 5 | use super::network::{Messenger, Protocol}; 6 | use super::{get_tcpstream_ok, TransportOptions, TransportProtocol}; 7 | use crate::{ 8 | error::{self, Result}, 9 | transport::NetworkInfo, 10 | }; 11 | 12 | pub type TcpProtocol = TransportProtocol; 13 | 14 | impl TcpProtocol { 15 | pub fn new(trp_options: &TransportOptions) -> Result { 16 | let (handle, _) = get_tcpstream_ok(&trp_options.endpoint.addrs[..], trp_options.timeout)?; 17 | 18 | handle 19 | .set_read_timeout(Some(trp_options.timeout)) 20 | .map_err(|e| crate::error::Error::Timeout(e, trp_options.timeout))?; 21 | handle 22 | .set_write_timeout(Some(trp_options.timeout)) 23 | .map_err(|e| crate::error::Error::Timeout(e, trp_options.timeout))?; 24 | 25 | let peer = handle.peer_addr().ok(); 26 | debug!("created TCP socket to {:?}", peer); 27 | 28 | Ok(Self { 29 | handle, 30 | netinfo: NetworkInfo { 31 | sent: 0, 32 | received: 0, 33 | peer, 34 | }, 35 | }) 36 | } 37 | } 38 | 39 | impl Messenger for TcpProtocol { 40 | async fn asend(&mut self, _: &[u8]) -> error::Result { 41 | Ok(0) 42 | } 43 | async fn arecv(&mut self, _: &mut [u8]) -> error::Result { 44 | Ok(0) 45 | } 46 | 47 | async fn aconnect(&mut self) -> error::Result<()> { 48 | Ok(()) 49 | } 50 | 51 | fn send(&mut self, buffer: &[u8]) -> Result { 52 | self.netinfo.sent = self.handle.write(buffer).map_err(crate::error::Error::Buffer)?; 53 | self.handle.flush().map_err(crate::error::Error::Buffer)?; 54 | Ok(self.netinfo.sent) 55 | } 56 | 57 | fn recv(&mut self, buffer: &mut [u8]) -> Result { 58 | self.netinfo.received = super::tcp_read(&mut self.handle, buffer)?; 59 | Ok(self.netinfo.received) 60 | } 61 | 62 | fn uses_leading_length(&self) -> bool { 63 | true 64 | } 65 | 66 | fn mode(&self) -> Protocol { 67 | Protocol::Tcp 68 | } 69 | 70 | fn network_info(&self) -> &NetworkInfo { 71 | self.netinfo() 72 | } 73 | 74 | // fn local(&self) -> std::io::Result { 75 | // self.handle.local_addr() 76 | // } 77 | 78 | // fn peer(&self) -> std::io::Result { 79 | // self.handle.peer_addr() 80 | // } 81 | } 82 | -------------------------------------------------------------------------------- /src/transport/tls.rs: -------------------------------------------------------------------------------- 1 | // Specific TLS handling 2 | use std::{ 3 | io::Write, 4 | net::{SocketAddr, TcpStream}, 5 | sync::Arc, 6 | }; 7 | 8 | use log::{debug, info}; 9 | use rustls::{ClientConnection, StreamOwned}; 10 | use rustls_pki_types::ServerName; 11 | 12 | use super::{ 13 | crypto::{root_store, tls_config}, 14 | endpoint::EndPoint, 15 | network::{Messenger, Protocol}, 16 | }; 17 | use super::{get_tcpstream_ok, TransportOptions, TransportProtocol}; 18 | use crate::{ 19 | error::{self, Dns, Error, Network, Result}, 20 | transport::NetworkInfo, 21 | }; 22 | 23 | pub type TlsProtocol = TransportProtocol>; 24 | 25 | // ALPN bytes as stated here: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml 26 | const ALPN_DOT: &[u8] = b"dot"; 27 | 28 | impl TlsProtocol { 29 | pub fn new(trp_options: &TransportOptions) -> Result { 30 | // First we load some root certificates. These are used to authenticate the server. 31 | // The recommended way is to depend on the webpki_roots crate which contains the Mozilla set of root certificates. 32 | let root_store = root_store(&trp_options.cert)?; 33 | debug!("built root store with {} CAs", root_store.len()); 34 | 35 | // Next, we make a ClientConfig. You’re likely to make one of these per process, and use it for all connections made by that process. 36 | let mut config = tls_config(root_store); 37 | 38 | if trp_options.alpn { 39 | config.alpn_protocols = vec![ALPN_DOT.to_vec()]; 40 | } 41 | 42 | // as EndPoint addrs can contain several addresses, we get the first address for which 43 | // we can create a TcpStream. This is the case when we pass e.g.: one.one.one.one:853 44 | let (stream, addr) = get_tcpstream_ok(&trp_options.endpoint.addrs[..], trp_options.timeout)?; 45 | debug!("created TLS-TCP socket to {}", addr); 46 | 47 | let server_name = Self::build_server_name(&trp_options.endpoint, &addr)?; 48 | debug!("server name: {:?}", server_name); 49 | 50 | let conn = ClientConnection::new(Arc::new(config), server_name).map_err(Error::Tls)?; 51 | let tls_stream = StreamOwned::new(conn, stream); 52 | 53 | let peer = tls_stream.sock.peer_addr().ok(); 54 | 55 | Ok(Self { 56 | handle: tls_stream, 57 | netinfo: NetworkInfo { 58 | sent: 0, 59 | received: 0, 60 | peer, 61 | }, 62 | }) 63 | } 64 | 65 | // build server name used by ClientConnection::new() 66 | fn build_server_name(ep: &EndPoint, addr: &SocketAddr) -> Result> { 67 | // use SNI if set 68 | if let Some(sni) = &ep.sni { 69 | ServerName::try_from(sni.clone()).map_err(|_| Error::Dns(Dns::InvalidSNI)) 70 | } 71 | // or target ip addr 72 | else { 73 | Ok(ServerName::from(addr.ip())) 74 | } 75 | } 76 | } 77 | 78 | impl Messenger for TlsProtocol { 79 | async fn asend(&mut self, _: &[u8]) -> error::Result { 80 | Ok(0) 81 | } 82 | async fn arecv(&mut self, _: &mut [u8]) -> error::Result { 83 | Ok(0) 84 | } 85 | 86 | async fn aconnect(&mut self) -> error::Result<()> { 87 | Ok(()) 88 | } 89 | 90 | fn send(&mut self, buffer: &[u8]) -> Result { 91 | self.netinfo.sent = self 92 | .handle 93 | .write(buffer) 94 | .map_err(|e| Error::Network(e, Network::Send))?; 95 | 96 | if let Some(cs) = self.handle.conn.negotiated_cipher_suite() { 97 | info!("negociated ciphersuite: {:?}", cs); 98 | } 99 | 100 | Ok(self.netinfo.sent) 101 | } 102 | 103 | fn recv(&mut self, buffer: &mut [u8]) -> Result { 104 | self.netinfo.received = super::tcp_read(&mut self.handle, buffer)?; 105 | Ok(self.netinfo.received) 106 | } 107 | 108 | fn uses_leading_length(&self) -> bool { 109 | true 110 | } 111 | 112 | fn mode(&self) -> Protocol { 113 | Protocol::DoT 114 | } 115 | 116 | fn network_info(&self) -> &NetworkInfo { 117 | self.netinfo() 118 | } 119 | 120 | // fn local(&self) -> std::io::Result { 121 | // self.handle.sock.local_addr() 122 | // } 123 | 124 | // fn peer(&self) -> std::io::Result { 125 | // self.handle.sock.peer_addr() 126 | // } 127 | } 128 | -------------------------------------------------------------------------------- /src/transport/udp.rs: -------------------------------------------------------------------------------- 1 | use std::net::UdpSocket; 2 | 3 | use log::debug; 4 | 5 | use super::network::{Messenger, Protocol}; 6 | use super::{TransportOptions, TransportProtocol}; 7 | use crate::error::{self, Error, Network, Result}; 8 | use crate::transport::NetworkInfo; 9 | 10 | pub type UdpProtocol = TransportProtocol; 11 | 12 | impl UdpProtocol { 13 | pub fn new(trp_options: &TransportOptions) -> Result { 14 | let unspec = trp_options.ip_version.unspecified_ip_vec(); 15 | let sock = UdpSocket::bind(&unspec[..]).map_err(|e| Error::Network(e, Network::Bind))?; 16 | 17 | debug!( 18 | "bound UDP socket to {}", 19 | sock.local_addr().map_err(|e| Error::Network(e, Network::LocalAddr))? 20 | ); 21 | 22 | sock.set_read_timeout(Some(trp_options.timeout)) 23 | .map_err(|e| Error::Timeout(e, trp_options.timeout))?; 24 | sock.set_write_timeout(Some(trp_options.timeout)) 25 | .map_err(|e| Error::Timeout(e, trp_options.timeout))?; 26 | 27 | // connect() will chose any socket address which is succesful 28 | // as TransportOptions impl ToSocketAddrs 29 | sock.connect(&trp_options.endpoint.addrs[..]) 30 | .map_err(|e| Error::Network(e, Network::Connect))?; 31 | 32 | let peer = sock.peer_addr().ok(); 33 | debug!("created UDP socket to {:?}", peer); 34 | 35 | Ok(Self { 36 | handle: sock, 37 | netinfo: NetworkInfo { 38 | sent: 0, 39 | received: 0, 40 | peer, 41 | }, 42 | }) 43 | } 44 | 45 | // // display list of found host resolvers and try to bind 46 | // pub fn list_resolvers(trp_options: &TransportOptions) -> Result<()> { 47 | // // create udp socket on either V4 or V6 48 | // let unspec = trp_options.ip_version.unspecified_ip(); 49 | // let sock = UdpSocket::bind(&unspec).map_err(|e| Error::Network(e, Network::Bind))?; 50 | 51 | // for addr in &trp_options.endpoint.addrs { 52 | // // try to connect 53 | // let result = if let Ok(_) = sock.connect(addr) { "OK" } else { " KO " }; 54 | // println!("addr: {}, connect: {} ", addr, result); 55 | // } 56 | 57 | // Ok(()) 58 | // } 59 | } 60 | 61 | impl Messenger for UdpProtocol { 62 | async fn asend(&mut self, _: &[u8]) -> error::Result { 63 | Ok(0) 64 | } 65 | async fn arecv(&mut self, _: &mut [u8]) -> error::Result { 66 | Ok(0) 67 | } 68 | 69 | async fn aconnect(&mut self) -> error::Result<()> { 70 | Ok(()) 71 | } 72 | 73 | fn send(&mut self, buffer: &[u8]) -> Result { 74 | self.netinfo.sent = self.handle.send(buffer).map_err(|e| Error::Network(e, Network::Send))?; 75 | debug!("sent {} bytes", self.netinfo.sent); 76 | 77 | Ok(self.netinfo.sent) 78 | } 79 | 80 | fn recv(&mut self, buffer: &mut [u8]) -> Result { 81 | self.netinfo.received = self 82 | .handle 83 | .recv(buffer) 84 | .map_err(|e| Error::Network(e, Network::Receive))?; 85 | debug!("received {} bytes", self.netinfo.received); 86 | 87 | Ok(self.netinfo.received) 88 | } 89 | 90 | fn uses_leading_length(&self) -> bool { 91 | false 92 | } 93 | 94 | fn mode(&self) -> Protocol { 95 | Protocol::Udp 96 | } 97 | 98 | fn network_info(&self) -> &NetworkInfo { 99 | self.netinfo() 100 | } 101 | 102 | // fn local(&self) -> std::io::Result { 103 | // self.handle.local_addr() 104 | // } 105 | 106 | // fn peer(&self) -> std::io::Result { 107 | // self.handle.peer_addr() 108 | // } 109 | } 110 | -------------------------------------------------------------------------------- /tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | use std::process::{Command, ExitStatus, Stdio}; 2 | 3 | #[cfg(target_family = "unix")] 4 | const DQY: &str = "./target/debug/dqy"; 5 | 6 | #[cfg(target_family = "windows")] 7 | const DQY: &str = ".\\target\\debug\\dqy.exe"; 8 | 9 | fn to_args(s: &str) -> Vec<&str> { 10 | s.split_whitespace().collect() 11 | } 12 | 13 | fn call_dqy(args: &[&str]) -> std::io::Result { 14 | Command::new(DQY).args(args).stdout(Stdio::null()).status() 15 | } 16 | 17 | fn test_dqy_call(args: &str) { 18 | let args = to_args(args); 19 | let status = call_dqy(&args).expect("error calling dqy exe"); 20 | assert!(status.code().is_some()); 21 | assert_eq!(status.code().unwrap(), 0); 22 | } 23 | 24 | macro_rules! test_dqy { 25 | ($fn_name:ident, $args:literal, $is_ipv6:literal) => { 26 | #[test] 27 | fn $fn_name() { 28 | // don't use IPV6 on github action runners because it's not yet supported 29 | if $is_ipv6 || std::env::var("GITHUB_REPOSITORY").is_err() { 30 | test_dqy_call($args); 31 | } 32 | } 33 | }; 34 | } 35 | 36 | // test transports 37 | test_dqy!(udp, "A www.google.com", false); 38 | test_dqy!(udp6, "A www.google.com -6", true); 39 | test_dqy!(tcp, "A www.google.com --tcp", false); 40 | test_dqy!(tcp6, "A www.google.com --tcp -6", true); 41 | test_dqy!(tls, "A www.google.com @1.1.1.1 --tls", false); 42 | test_dqy!(tls6, "A www.google.com @2606:4700:4700::1001 --tls", true); 43 | test_dqy!( 44 | doh, 45 | "A www.google.fr @https://mozilla.cloudflare-dns.com/dns-query --https", 46 | false 47 | ); 48 | test_dqy!( 49 | doh6, 50 | "A www.google.fr @https://mozilla.cloudflare-dns.com/dns-query --https -6", 51 | true 52 | ); 53 | test_dqy!(doq, "A www.google.com @quic://dns.adguard.com", false); 54 | test_dqy!(doq6, "A www.google.com @quic://dns.adguard.com -6", true); 55 | 56 | // endpoints 57 | test_dqy!(one, "A www.google.com @one.one.one.one", false); 58 | test_dqy!(one6, "A www.google.com @one.one.one.one -6", true); 59 | test_dqy!(one_tcp, "A www.google.com @one.one.one.one --tcp", false); 60 | test_dqy!(one_tcp6, "A www.google.com @one.one.one.one --tcp -6", true); 61 | test_dqy!(one_port, "A www.google.com @one.one.one.one:53", false); 62 | test_dqy!(one_port6, "A www.google.com @one.one.one.one:53 -6", true); 63 | 64 | // IDNA 65 | test_dqy!(german, "A münchen.de", false); 66 | test_dqy!(cyrillic, "A россия.рф", false); 67 | test_dqy!(greek, "AAAA ουτοπία.δπθ.gr", false); 68 | test_dqy!(korean, "A 스타벅스코리아.com", false); 69 | test_dqy!(nordic, "A www.øl.com", false); 70 | test_dqy!(chinese, "A 香港.中國", false); 71 | 72 | // Misc 73 | test_dqy!(dropbox, "TXT dropbox.com", false); 74 | test_dqy!( 75 | zoneversion, 76 | "@ns1-dyn.bortzmeyer.fr dyn.bortzmeyer.fr SOA --zoneversion", 77 | false 78 | ); 79 | test_dqy!(wallet, "bortzmeyer.fr WALLET", false); 80 | test_dqy!(type262, "bortzmeyer.fr type262", false); 81 | test_dqy!(axfr, "axfr @nsztm1.digi.ninja zonetransfer.me", false); 82 | test_dqy!(any, "@a.gtld-servers.net ANY com.", false); 83 | test_dqy!(https, "HTTPS arts.fr", false); 84 | test_dqy!(caa, "CAA duckduckgo.com", false); 85 | test_dqy!(ptr, "-x 3.209.40.246", false); 86 | test_dqy!(mx, "mx yahoo.com", false); 87 | -------------------------------------------------------------------------------- /tests/pcap/a.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/a.pcap -------------------------------------------------------------------------------- /tests/pcap/aaaa.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/aaaa.pcap -------------------------------------------------------------------------------- /tests/pcap/afsdb.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/afsdb.pcap -------------------------------------------------------------------------------- /tests/pcap/apl.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/apl.pcap -------------------------------------------------------------------------------- /tests/pcap/caa.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/caa.pcap -------------------------------------------------------------------------------- /tests/pcap/cap1.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cap1.pcap -------------------------------------------------------------------------------- /tests/pcap/cap2.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cap2.pcap -------------------------------------------------------------------------------- /tests/pcap/cap3.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cap3.pcap -------------------------------------------------------------------------------- /tests/pcap/cap4.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cap4.pcap -------------------------------------------------------------------------------- /tests/pcap/cdnskey.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cdnskey.pcap -------------------------------------------------------------------------------- /tests/pcap/cds.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cds.pcap -------------------------------------------------------------------------------- /tests/pcap/cert.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cert.pcap -------------------------------------------------------------------------------- /tests/pcap/cname.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/cname.pcap -------------------------------------------------------------------------------- /tests/pcap/create_sample_data.sh: -------------------------------------------------------------------------------- 1 | #/usr/bin/bash 2 | 3 | # runs dig commands and tcpdump to capture data for tests 4 | 5 | # cap1: plain DNS query to google.com without OPT 6 | # tcpdump -c 2 -n -w cap1.pcap host 1.1.1.1 2>/dev/null & 7 | # sleep 1 8 | # dig @1.1.1.1 A www.google.com +noedns 1>/dev/null 9 | 10 | # # cap2: NS to hk. domain 11 | # tcpdump -c 2 -n -w cap2.pcap host 1.1.1.1 2>/dev/null & 12 | # sleep 1 13 | # dig @1.1.1.1 NS hk. 1>/dev/null 14 | 15 | # # cap3: DNSKEY to hk. domain 16 | # tcpdump -c 2 -n -w cap3.pcap host 1.1.1.1 2>/dev/null & 17 | # sleep 1 18 | # dig @1.1.1.1 DNSKEY hk. 1>/dev/null 19 | 20 | # cap4: A to . domain 21 | tcpdump -c 2 -n -w cap4.pcap host 198.41.0.4 2>/dev/null & 22 | sleep 1 23 | dig ns2.google.com. @198.41.0.4 1>/dev/null 24 | -------------------------------------------------------------------------------- /tests/pcap/csync.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/csync.pcap -------------------------------------------------------------------------------- /tests/pcap/dhcid.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/dhcid.pcap -------------------------------------------------------------------------------- /tests/pcap/dlv.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/dlv.pcap -------------------------------------------------------------------------------- /tests/pcap/dname.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/dname.pcap -------------------------------------------------------------------------------- /tests/pcap/dnskey.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/dnskey.pcap -------------------------------------------------------------------------------- /tests/pcap/ds.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/ds.pcap -------------------------------------------------------------------------------- /tests/pcap/eui48.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/eui48.pcap -------------------------------------------------------------------------------- /tests/pcap/eui64.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/eui64.pcap -------------------------------------------------------------------------------- /tests/pcap/foo.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/foo.pcapng -------------------------------------------------------------------------------- /tests/pcap/get_wire_data.sh: -------------------------------------------------------------------------------- 1 | #/usr/bin/bash 2 | 3 | # runs dig commands and tcpdump to capture data for tests 4 | set -x 5 | 6 | # start tcpdump to capture packets 7 | tcpdump -i enp5s0 -c 100 -n -w $1.pcap host 45.90.28.55 & 8 | tcpdump_pid=$! 9 | echo "process ID=$tcpdump_pid" 10 | sleep 1 11 | 12 | # start dig to send/receive DNS packets 13 | dig $2 $1 $1.dns.netmeister.org. +short 14 | sleep 2 15 | 16 | # gracedully kill tcpdump 17 | kill -INT $tcpdump_pid 18 | 19 | # compare with dqy 20 | /data/projects/rust/dqy/target/debug/dqy $1 $1.dns.netmeister.org. -------------------------------------------------------------------------------- /tests/pcap/hinfo.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/hinfo.pcap -------------------------------------------------------------------------------- /tests/pcap/hip.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/hip.pcap -------------------------------------------------------------------------------- /tests/pcap/ipseckey.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/ipseckey.pcap -------------------------------------------------------------------------------- /tests/pcap/kx.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/kx.pcap -------------------------------------------------------------------------------- /tests/pcap/loc.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/loc.pcap -------------------------------------------------------------------------------- /tests/pcap/mx.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/mx.pcap -------------------------------------------------------------------------------- /tests/pcap/naptr.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/naptr.pcap -------------------------------------------------------------------------------- /tests/pcap/ns.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/ns.pcap -------------------------------------------------------------------------------- /tests/pcap/nsec.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/nsec.pcap -------------------------------------------------------------------------------- /tests/pcap/nsec3.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/nsec3.pcap -------------------------------------------------------------------------------- /tests/pcap/nsec3param.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/nsec3param.pcap -------------------------------------------------------------------------------- /tests/pcap/openpgpkey.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/openpgpkey.pcap -------------------------------------------------------------------------------- /tests/pcap/ptr.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/ptr.pcap -------------------------------------------------------------------------------- /tests/pcap/rp.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/rp.pcap -------------------------------------------------------------------------------- /tests/pcap/rrsig.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/rrsig.pcap -------------------------------------------------------------------------------- /tests/pcap/smimea.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/smimea.pcap -------------------------------------------------------------------------------- /tests/pcap/soa.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/soa.pcap -------------------------------------------------------------------------------- /tests/pcap/srv.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/srv.pcap -------------------------------------------------------------------------------- /tests/pcap/sshfp.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/sshfp.pcap -------------------------------------------------------------------------------- /tests/pcap/svcb.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/svcb.pcap -------------------------------------------------------------------------------- /tests/pcap/tkey.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/tkey.pcap -------------------------------------------------------------------------------- /tests/pcap/tlsa.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/tlsa.pcap -------------------------------------------------------------------------------- /tests/pcap/txt.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/txt.pcap -------------------------------------------------------------------------------- /tests/pcap/uri.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/uri.pcap -------------------------------------------------------------------------------- /tests/pcap/zonemd.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dandyvica/dqy/c2b28be3d185360f9e94a92da95d318c08db9926/tests/pcap/zonemd.pcap -------------------------------------------------------------------------------- /tests/resolv.conf: -------------------------------------------------------------------------------- 1 | nameserver 1.1.1.1 -------------------------------------------------------------------------------- /tests/test_bind9.sh: -------------------------------------------------------------------------------- 1 | # dqy tests using venom and bind9 bespoke configuration 2 | 3 | # test internet resources 4 | venom run venom/internet.yml 5 | 6 | # test BIND9 server on Docker 7 | echo "testing on udp" 8 | venom run venom/bind9.yml --var="target='@localhost'" --var="opts='-p 10053'" 9 | 10 | echo "testing on tcp" 11 | venom run venom/bind9.yml --var="target='@localhost'" --var="opts='-p 10053 --tcp'" 12 | 13 | echo "testing on DoT" 14 | venom run venom/bind9.yml --var="target='@localhost'" --var="opts='-p 10853 --dot --cert bind9/cert/cert.der'" 15 | 16 | echo "testing on DoH" 17 | venom run venom/bind9.yml --var="target='@https://localhost:10443/dns-query'" --var="opts='--doh --cert bind9/cert/cert.pem'" -------------------------------------------------------------------------------- /tests/tpl.hbs: -------------------------------------------------------------------------------- 1 | Hello {{ info.netinfo.peer }} 2 | 3 | {{#each messages}} 4 | {{#each response.answer}} 5 | {{name}} {{type}} <{{rdata}}> 6 | {{/each}} 7 | {{/each}} -------------------------------------------------------------------------------- /tests/venom/bind9.yml: -------------------------------------------------------------------------------- 1 | name: DQY binary test 2 | vars: 3 | const: --no-colors --short 4 | testcases: 5 | - name: Testing DNS records with {{.const}} {{.opts}} 6 | steps: 7 | 8 | - script: dqy A {{.target}} a.dqy-test.dns {{.const}} {{.opts}} 9 | type: exec 10 | assertions: 11 | - result.code ShouldEqual 0 12 | - result.systemout ShouldEqual "10.0.100.100" 13 | 14 | - script: dqy AAAA {{.target}} aaaa.dqy-test.dns {{.const}} {{.opts}} 15 | assertions: 16 | - result.code ShouldEqual 0 17 | - result.systemout ShouldEqual "b567:6013:e52f:c164:cf0f:1276:aebd:f634" 18 | 19 | - script: dqy TXT {{.target}} txt.dqy-test.dns {{.const}} {{.opts}} 20 | assertions: 21 | - result.code ShouldEqual 0 22 | - result.systemout ShouldEqual "local network" 23 | 24 | - script: dqy HINFO {{.target}} hinfo.dqy-test.dns {{.const}} {{.opts}} 25 | assertions: 26 | - result.code ShouldEqual 0 27 | - result.systemout ShouldEqual "x86_64 Linux" 28 | -------------------------------------------------------------------------------- /tests/venom/bind9/Dockerfile: -------------------------------------------------------------------------------- 1 | # used to test dqy 2 | 3 | FROM ubuntu/bind9 4 | 5 | COPY db.dqy-test.dns /etc/bind 6 | COPY named.conf.local /etc/bind 7 | COPY named.conf.options /etc/bind 8 | COPY cert/cert.pem /etc/bind 9 | COPY cert/key.pem /etc/bind 10 | 11 | RUN <