diff --git a/Cargo.lock b/Cargo.lock index 768e0a15..f9bacc0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9908,8 +9908,6 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-dns" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6f884fa9a8d48101774bfbd3aeb81e968dd22cffd19a372da69f183db22c1a" dependencies = [ "bitflags 2.13.1", ] @@ -11543,6 +11541,7 @@ dependencies = [ "gpui-component-assets", "gpui_platform", "image", + "iroh", "jieba-rs", "keyring", "ksni", @@ -11567,6 +11566,7 @@ dependencies = [ "smallvec", "smol", "tempfile", + "tokio", "tray-icon", "tty7-core", "tty7-gateway", diff --git a/Cargo.toml b/Cargo.toml index ea272938..c13fa30a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -274,6 +274,10 @@ ksni = { version = "0.3.6", default-features = false, features = ["blocking", "a # into test builds and never reaches a release binary. [dev-dependencies] gpui = { workspace = true, features = ["test-support"] } +# tests/edns_z_flag.rs drives iroh's resolver, so the simple-dns patch below is +# checked by the default `cargo test` CI runs, which skips tty7-gateway. +iroh = "1.2" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } [lints] workspace = true @@ -366,6 +370,11 @@ smol = "2" # to an exact rev (never a branch): russh is the SSH protocol layer handling # user credentials, and `cargo update` must not change what it builds. russh = { git = "https://github.com/l0ng-ai/russh", rev = "34f73e68d875b6fc2529654102ee65bafc691c59" } +# simple-dns 0.12.0 with the EDNS OPT TTL read at RFC 6891's offsets: upstream +# takes the extended RCODE from the low byte, where the DO/Z flags live, so a +# resolver that sets a Z bit (Clash-style fake-IP DNS does) turns every answer +# into RCODE 16 and iroh resolves nothing — no relay, no phone off the LAN. +simple-dns = { path = "vendor/simple-dns" } [workspace.lints.clippy] dbg_macro = "deny" diff --git a/mobile/src-tauri/Cargo.lock b/mobile/src-tauri/Cargo.lock index 8aacb981..287c5649 100644 --- a/mobile/src-tauri/Cargo.lock +++ b/mobile/src-tauri/Cargo.lock @@ -4734,8 +4734,6 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-dns" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6f884fa9a8d48101774bfbd3aeb81e968dd22cffd19a372da69f183db22c1a" dependencies = [ "bitflags 2.13.2", ] diff --git a/mobile/src-tauri/Cargo.toml b/mobile/src-tauri/Cargo.toml index ba6a1c5a..be6a873a 100644 --- a/mobile/src-tauri/Cargo.toml +++ b/mobile/src-tauri/Cargo.toml @@ -56,3 +56,8 @@ block2 = "0.6" # the APK from 32 to 23. iOS strips at export either way. [profile.release] strip = true + +# The phone resolves relays with iroh's DNS resolver too: the same simple-dns +# fix as the root workspace's, from its vendored copy (see ../../Cargo.toml). +[patch.crates-io] +simple-dns = { path = "../../vendor/simple-dns" } diff --git a/tests/edns_z_flag.rs b/tests/edns_z_flag.rs new file mode 100644 index 00000000..47a5e898 --- /dev/null +++ b/tests/edns_z_flag.rs @@ -0,0 +1,70 @@ +//! The mobile gateway finds its relay through iroh's own DNS resolver, so an answer +//! that resolver cannot read leaves it with no relay, and phones on other +//! networks with nothing to dial. + +use std::net::{Ipv4Addr, SocketAddr, UdpSocket}; +use std::time::Duration; + +use iroh::dns::{DnsProtocol, DnsResolver}; + +/// Answers one A query for `answer`, with an EDNS OPT record whose TTL is +/// `opt_ttl`: [extended RCODE][version][DO + Z flags], per RFC 6891. +fn serve_one(socket: UdpSocket, answer: Ipv4Addr, opt_ttl: u32) { + let mut buf = [0u8; 512]; + let (len, from) = socket.recv_from(&mut buf).unwrap(); + let query = &buf[..len]; + // The question runs from the header to the end of its name, plus type + // and class. + let mut end = 12; + while query[end] != 0 { + end += 1 + query[end] as usize; + } + end += 1 + 4; + + let mut reply = Vec::new(); + reply.extend_from_slice(&query[..2]); // id + reply.extend_from_slice(&[0x81, 0x80]); // response, RD, RA, NOERROR + reply.extend_from_slice(&[0, 1, 0, 1, 0, 0, 0, 1]); // 1 question, 1 answer, 1 additional + reply.extend_from_slice(&query[12..end]); + // A record, its name a pointer to the question's. + reply.extend_from_slice(&[0xc0, 0x0c, 0, 1, 0, 1, 0, 0, 0, 60, 0, 4]); + reply.extend_from_slice(&answer.octets()); + // OPT: root name, type 41, a 1232-byte UDP payload, the TTL, no options. + reply.extend_from_slice(&[0, 0, 41, 0x04, 0xd0]); + reply.extend_from_slice(&opt_ttl.to_be_bytes()); + reply.extend_from_slice(&[0, 0]); + socket.send_to(&reply, from).unwrap(); +} + +async fn resolve_with_opt_ttl(opt_ttl: u32) -> Result, String> { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + let addr: SocketAddr = socket.local_addr().unwrap(); + let answer = Ipv4Addr::new(198, 18, 0, 4); + let server = std::thread::spawn(move || serve_one(socket, answer, opt_ttl)); + + // No fallback: a public resolver answering instead would hide the bug. + let resolver = DnsResolver::builder() + .with_nameserver(addr, DnsProtocol::Udp) + .disable_fallback() + .build(); + let result = resolver + .lookup_ipv4("relay.example.", Duration::from_secs(5)) + .await + .map(|ips| ips.collect()) + .map_err(|e| format!("{e:#}")); + server.join().unwrap(); + result +} + +/// A Z flag set in the OPT record is the low byte of its TTL. simple-dns +/// 0.12.0 read the extended RCODE from there, turning NOERROR into RCODE 16 +/// and failing every lookup — what a Clash-style fake-IP DNS on the router +/// sends. Fails if the `simple-dns` patch in the workspace Cargo.toml stops +/// applying. +#[tokio::test(flavor = "multi_thread")] +async fn an_answer_with_an_edns_z_flag_set_still_resolves() { + assert_eq!( + resolve_with_opt_ttl(0x0000_0001).await, + Ok(vec![Ipv4Addr::new(198, 18, 0, 4).into()]) + ); +} diff --git a/vendor/simple-dns/.cargo_vcs_info.json b/vendor/simple-dns/.cargo_vcs_info.json new file mode 100644 index 00000000..3c62b352 --- /dev/null +++ b/vendor/simple-dns/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "5c7ec98798d7c9f7d76df7aa3bbe754913ff5159" + }, + "path_in_vcs": "simple-dns" +} \ No newline at end of file diff --git a/vendor/simple-dns/CHANGELOG.md b/vendor/simple-dns/CHANGELOG.md new file mode 100644 index 00000000..c1d414b4 --- /dev/null +++ b/vendor/simple-dns/CHANGELOG.md @@ -0,0 +1,220 @@ +# 0.12.0 (2026-07-26) + +### Fix +- Document panic behaviour in header_buffer and change the functions to have a safe and _unchecked version. +- Fixed incorrect resource record len bytes when writing NSEC records +- Fixed incorrect behaviour when writing compressed resource records to fixed size writers + +# 0.11.3 (2026-05-15) + +### Fix +- Updated bitflags dependency to 2.11. +- Clippy run + +# 0.11.2 (2026-01-06) + +### Fix +- Fix name compression when the offset is too large PR [#53] + +# 0.11.1 (2025-12-27) + +### Fix +- Fix CharacterString display implementation to prevent panics + +# 0.11.0 (2025-07-23) + +### Added +- no-std support +- **std** and **alloc** feature + +# 0.10.1 (2025-04-20) + +### Fix +- Change Name::get_labels lifetime anotation to be less restrictive + +# 0.10.0 (2025-02-22) + +### Changes +- Changes internal implementation to use a Buffer struct instead of a byte slice +- Add bind9 compatibility tests + +### Fix +- Fix NSec parsing code + +### Breaking changes +- Add SVCB Params for SVCB resource records + +# 0.9.3 (2025-01-18) + +### Fix +- Fix Name and Label display implementation + +# 0.9.2 (2025-01-12) + +### Fix +- Fix panic when parsing (PR #40) + +# 0.9.1 (2024-11-30) + +### Fix +- Allow labels starting with numbers (RFC-1123) + +### Added +- Add CERT, DNSKEY, DS, EUI48, EUI64, IPSECKEY, KX, RRSIG, ZONEMD, NSEC, DHCID support + +# 0.9.0 (2024-10-17) + +### Fix +- Correct serialization of OPT resource record for eDNS packets + +### Added +- Add `new_with_labels` to `Name` implementation +- Exposes `Label` type + +### Breaking Changes +- Add data validation to `Name::new`. + +# 0.8.0 (2024-08-27) + +### Fix (Breaking) +- Remove the length octet from CAA value serialization + +# 0.7.1 (2024-08-13) + +### Added +- Add `set_id` function to packet + +# 0.7.0 (2024-03-25) + +### Fix (Breaking) +- Handle empty rdata parsing (when the lenght is 0) + +# 0.6.2 (2024-02-27) + +### Added +- Add NAPTR record type parsing (RFC 3403) + +# 0.6.1 (2024-02-11) + +### Fix +- Fixes invalid name lengths when generating uncompressed bytes from a packet that was parsed from compressed data. + +# 0.6.0 (2024-01-06) + +### Fix +- Fixes invalid rdata length when generating compressed packets. + +### Changed (Breaking) +- Anotate TYPE with non_exhaustive +- QCLASS::match_qtype no longer matcher A and AAAA together + +### Added (Breaking) +- Add SVBC record type parsing (RFC 9460) + +# 0.5.7 (2023-10-17) + +### Fix +- TryFrom<'str> for TXT generating invalid CharacterStrings +- `Name::is_subdomain_of` no longer return true for the same domain + +### Added +- `Name::without` to extract subdomains from a domain + +# 0.5.6 (2023-10-04) + +### Fix +- Name compression now considers the full domain when creating pointers. + +### Added +- Add TryFrom<'str> and TryInto<'str> for TXT records as a convenience to work with long TXT records + +# 0.5.5 (2023-09-14) + +### Fix +- Add derived traits to PacketFlag that where removed by the previous version + +# 0.5.4 (2023-09-07) + +### Added +- Support to parse CAA records + +# 0.5.3 (2023-07-09) + +### Added +- `write_to` and `write_compressed_to` functions to `Packet` + +# 0.5.2 (2023-03-17) +- Add DeRef and DerefMut implemetations for the ResourceRecord wrapper macro + +# 0.5.1 (2023-01-17) + +### Fixed +- Prevent panic! when trying to parse an empty slice + +# 0.5.0 (2022-12-10) + +### Fixed +- Fixes parsing for Name, CharacterString and SOA types + +### Changed (Breaking) +- Header flags now use crate [bitflags](https://crates.io/crates/bitflags) +- Removed PacketBuf struct, due to how EDNS0 packets are constructed. +It is necessary to parse the whole packet to be able to construct the header information, which renders the PacketBuf *on the fly* approach unreliable + +### Added +- Add OPT record type from RFC 6891 (EDNS0 support) +- header_buffer module for packet header manipulation +- tests using sample files from bind9 + + +# 0.4.7 + +- Remove `thiserror` dependency +- Add RFC 2136 +- Add RFC 1183 +- Add RFC 1706 +- Add RFC 1876 +- Add NOTIFY code (RFC 1995 and 1996) +- Add new error type `InsufficientData` for stream parsing + +# 0.4.6 +- Add cache flush bit parsing to resource records + +# 0.4.5 +- Fix TXT records length + +# 0.4.4 +-Fix PTR parsing with compression + +# 0.4.3 +- Improve error handling + +# 0.4.2 +- Fix Name compression when using multiple pointers + +# 0.4.1 +- Fix a panic when parsing TXT records of length 0 + +# 0.4.0 +- Change internal data references to use Cow +- Add **into_owned** function to every resource +- Add **is_subdomain_of** for Name comparison +- Change RData::SRV to not use a Box anymore + +# 0.3.0 +- Fix TXT Resource Record implementation +- Drop byteorder crate dependency + +# 0.2.1 +- Fix Name hash function +- Fix SRV compression + +# 0.2.0 + +- Add Name compression +- Change DnsPacketContent visibility to pub(crate) +- Rename PacketSectionIter to QuestionsIter + +# 0.1.0 + +Initial project release diff --git a/vendor/simple-dns/Cargo.lock b/vendor/simple-dns/Cargo.lock new file mode 100644 index 00000000..34125d3d --- /dev/null +++ b/vendor/simple-dns/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "simple-dns" +version = "0.12.0" +dependencies = [ + "bitflags", +] diff --git a/vendor/simple-dns/Cargo.toml b/vendor/simple-dns/Cargo.toml new file mode 100644 index 00000000..f5d87ae8 --- /dev/null +++ b/vendor/simple-dns/Cargo.toml @@ -0,0 +1,54 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "simple-dns" +version = "0.12.0" +authors = ["Ilson Roberto Balliego Junior "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Rust implementation to parse and write DNS packets" +readme = "README.md" +keywords = [ + "dns", + "parser", +] +categories = [ + "parser-implementations", + "network-programming", + "no-std", +] +license = "MIT" +repository = "https://github.com/balliegojr/simple-dns" + +[package.metadata.docs.rs] +all-features = true + +[features] +alloc = [] +default = ["std"] +std = [] + +[lib] +name = "simple_dns" +path = "src/lib.rs" + +[[test]] +name = "packet_tests" +path = "tests/packet_tests.rs" + +[dependencies.bitflags] +version = "2.11" diff --git a/vendor/simple-dns/Cargo.toml.orig b/vendor/simple-dns/Cargo.toml.orig new file mode 100644 index 00000000..5447f696 --- /dev/null +++ b/vendor/simple-dns/Cargo.toml.orig @@ -0,0 +1,22 @@ +[package] +name = "simple-dns" +version = "0.12.0" +authors = ["Ilson Roberto Balliego Junior "] +edition = "2021" +description = "Rust implementation to parse and write DNS packets" +repository = "https://github.com/balliegojr/simple-dns" +license = "MIT" +keywords = ["dns", "parser"] +categories = ["parser-implementations", "network-programming", "no-std"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["std"] +std = [] +alloc = [] + +[dependencies] +bitflags = "2.11" + +[package.metadata.docs.rs] +all-features = true diff --git a/vendor/simple-dns/LICENSE b/vendor/simple-dns/LICENSE new file mode 100644 index 00000000..74aec1a0 --- /dev/null +++ b/vendor/simple-dns/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ilson Roberto Balliego Junior + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/simple-dns/README.md b/vendor/simple-dns/README.md new file mode 100644 index 00000000..bcef4799 --- /dev/null +++ b/vendor/simple-dns/README.md @@ -0,0 +1,109 @@ +# Simple DNS +Pure Rust implementation to work with DNS packets + +You can parse or write a DNS packet by using [Packet](`Packet`) + +## Packet + +A `Packet` represents a dns packet, it is the main structure to construct and manipulate a packet before writing it into wire format. + +```rust +use simple_dns::*; +use simple_dns::rdata::*; + +let mut packet = Packet::new_query(1); + +let question = Question::new(Name::new_unchecked("_srv._udp.local"), TYPE::TXT.into(), CLASS::IN.into(), false); +packet.questions.push(question); + +let resource = ResourceRecord::new(Name::new_unchecked("_srv._udp.local"), CLASS::IN, 10, RData::A(A { address: 10 })); +packet.additional_records.push(resource); + +// Write the packet in the provided buffer; +let mut bytes = [0u8; 200]; +assert!(packet.write_to(&mut &mut bytes[..]).is_ok()); + +// Same as above, but allocates and returns a Vec +let bytes = packet.build_bytes_vec(); +assert!(bytes.is_ok()); + +// Same as above, but Names are compressed +let bytes = packet.build_bytes_vec_compressed(); +assert!(bytes.is_ok()); + +``` +It doesn't matter what order the resources are added, the packet will be built only when `build_bytes_vec` or `write_to` is called + +To parse the contents of a buffer into a packet, you need call call [Packet::parse] +```rust +use simple_dns::Packet; + +let bytes = b"\x00\x03\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"; +let packet = Packet::parse(&bytes[..]); +assert!(packet.is_ok()); +``` + +It is possible to check some information about a packet withouth parsing the packet, by using the `header_buffer` module functions. +Be cautious when checking **RCODE** and packet flags, see the module documentation for more information. + +```rust +use simple_dns::{header_buffer, PacketFlag}; +let buffer = b"\x00\x03\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"; + +assert_eq!(Ok(3), header_buffer::id(&buffer[..])); +assert!(!header_buffer::has_flags(&buffer[..], PacketFlag::RESPONSE).unwrap()); +``` + +EDNS is supported by Packet [opt](Packet::opt) and [opt_mut](Packet::opt_mut) functions, when working with ENDS packets, +you **SHOULD NOT** add **OPT Resource Records** directly to the **Additional Records** sections unless you know exactly what you are doing. + +## Feature Flags + +- `[std]`: Enables rust std +- `alloc`: Required for no_std support + + + + +## EDNS0 caveats + +EDNS extends the DNS packet header by adding an OPT resource record and *moving* part of the header information to the additional records section. +RCODE went from 4 bits to 12 bits, where the first 4 bits are stored in the header section and the last 8 bits are stored somewhere else inside the packet. + +This has some implications on how a packet can be parsed or build +``` +use simple_dns::{header_buffer, RCODE, Packet}; + +let buffer = b"\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x2e\x00\x00\x29\x01\xf4\x00\x00\x03\x01\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; +let packet = Packet::parse(&buffer[..]).unwrap(); + +// Without parsing the full packet, it is impossible to know the true RCODE of the packet +assert_eq!(RCODE::NoError, header_buffer::rcode(&buffer[..]).unwrap()); +assert_eq!(RCODE::BADVERS, packet.rcode()); +``` + +Please, refer to [RFC 6891](https://datatracker.ietf.org/doc/html/rfc6891) for more information + + +## DNS Packet Parser/Builder +The *Packet* structure provides parsing e building of a DNS packet, it aims to be fully compliant with the RFCs bellow: +- [RFC 1034](https://tools.ietf.org/html/rfc1034) +- [RFC 1035](https://tools.ietf.org/html/rfc1035) +- [RFC 1138](https://tools.ietf.org/html/rfc1138) +- [RFC 1183](https://tools.ietf.org/html/rfc1183) +- [RFC 1706](https://tools.ietf.org/html/rfc1706) +- [RFC 1876](https://tools.ietf.org/html/rfc1876) +- [RFC 1996](https://tools.ietf.org/html/rfc1996) +- [RFC 2136](https://tools.ietf.org/html/rfc2136) +- [RFC 6762](https://tools.ietf.org/html/rfc6762) +- [RFC 2782](https://tools.ietf.org/html/rfc2782) +- [RFC 3596](https://tools.ietf.org/html/rfc3596) +- [RFC 6891](https://datatracker.ietf.org/doc/html/rfc6891) +- [RFC 9460](https://datatracker.ietf.org/doc/html/rfc9460) + +Other Resource Records defined by other RFCs that are not in this list will be implemented over time + +# Update packets (RFC 2136) + +This library can parse update packets, however, it does not validate update rules and the update fields are overloaded in the packet fields, as defined in the RFC 2136. + diff --git a/vendor/simple-dns/samples/zonefile/A.sample.A b/vendor/simple-dns/samples/zonefile/A.sample.A new file mode 100644 index 00000000..c3fc5871 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/A.sample.A differ diff --git a/vendor/simple-dns/samples/zonefile/A.sample.VAXA b/vendor/simple-dns/samples/zonefile/A.sample.VAXA new file mode 100644 index 00000000..ecf41728 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/A.sample.VAXA differ diff --git a/vendor/simple-dns/samples/zonefile/A.sample.VENERA b/vendor/simple-dns/samples/zonefile/A.sample.VENERA new file mode 100644 index 00000000..c80e4c4e Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/A.sample.VENERA differ diff --git a/vendor/simple-dns/samples/zonefile/AAAA.sample b/vendor/simple-dns/samples/zonefile/AAAA.sample new file mode 100644 index 00000000..c6e29b98 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/AAAA.sample differ diff --git a/vendor/simple-dns/samples/zonefile/AFSDB.sample b/vendor/simple-dns/samples/zonefile/AFSDB.sample new file mode 100644 index 00000000..e9ce363c Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/AFSDB.sample differ diff --git a/vendor/simple-dns/samples/zonefile/CERT.sample b/vendor/simple-dns/samples/zonefile/CERT.sample new file mode 100644 index 00000000..f7a1bd88 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/CERT.sample differ diff --git a/vendor/simple-dns/samples/zonefile/DHCID.sample b/vendor/simple-dns/samples/zonefile/DHCID.sample new file mode 100644 index 00000000..4288e0d9 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/DHCID.sample differ diff --git a/vendor/simple-dns/samples/zonefile/DNSKEY.sample b/vendor/simple-dns/samples/zonefile/DNSKEY.sample new file mode 100644 index 00000000..d18b0a22 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/DNSKEY.sample differ diff --git a/vendor/simple-dns/samples/zonefile/DS.sample b/vendor/simple-dns/samples/zonefile/DS.sample new file mode 100644 index 00000000..0713b78f Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/DS.sample differ diff --git a/vendor/simple-dns/samples/zonefile/EUI48.sample b/vendor/simple-dns/samples/zonefile/EUI48.sample new file mode 100644 index 00000000..4cbe3a98 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/EUI48.sample differ diff --git a/vendor/simple-dns/samples/zonefile/EUI64.sample b/vendor/simple-dns/samples/zonefile/EUI64.sample new file mode 100644 index 00000000..e6b48424 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/EUI64.sample differ diff --git a/vendor/simple-dns/samples/zonefile/HINFO.sample b/vendor/simple-dns/samples/zonefile/HINFO.sample new file mode 100644 index 00000000..e9709236 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/HINFO.sample differ diff --git a/vendor/simple-dns/samples/zonefile/HTTPS.sample b/vendor/simple-dns/samples/zonefile/HTTPS.sample new file mode 100644 index 00000000..22cdea0d Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/HTTPS.sample differ diff --git a/vendor/simple-dns/samples/zonefile/IPSECKEY.sample b/vendor/simple-dns/samples/zonefile/IPSECKEY.sample new file mode 100644 index 00000000..7b89e044 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/IPSECKEY.sample differ diff --git a/vendor/simple-dns/samples/zonefile/ISDN.sample b/vendor/simple-dns/samples/zonefile/ISDN.sample new file mode 100644 index 00000000..6bae1513 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/ISDN.sample differ diff --git a/vendor/simple-dns/samples/zonefile/KX.sample b/vendor/simple-dns/samples/zonefile/KX.sample new file mode 100644 index 00000000..e57be338 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/KX.sample differ diff --git a/vendor/simple-dns/samples/zonefile/LOC.sample b/vendor/simple-dns/samples/zonefile/LOC.sample new file mode 100644 index 00000000..a8490367 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/LOC.sample differ diff --git a/vendor/simple-dns/samples/zonefile/MX.sample b/vendor/simple-dns/samples/zonefile/MX.sample new file mode 100644 index 00000000..dc2068b9 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/MX.sample differ diff --git a/vendor/simple-dns/samples/zonefile/NS.sample b/vendor/simple-dns/samples/zonefile/NS.sample new file mode 100644 index 00000000..1dd8137e Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/NS.sample differ diff --git a/vendor/simple-dns/samples/zonefile/NSAP-PTR.sample b/vendor/simple-dns/samples/zonefile/NSAP-PTR.sample new file mode 100644 index 00000000..acd51bf5 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/NSAP-PTR.sample differ diff --git a/vendor/simple-dns/samples/zonefile/NSAP.sample b/vendor/simple-dns/samples/zonefile/NSAP.sample new file mode 100644 index 00000000..582b1eac Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/NSAP.sample differ diff --git a/vendor/simple-dns/samples/zonefile/NSEC.sample b/vendor/simple-dns/samples/zonefile/NSEC.sample new file mode 100644 index 00000000..f62ee42c Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/NSEC.sample differ diff --git a/vendor/simple-dns/samples/zonefile/RP.sample b/vendor/simple-dns/samples/zonefile/RP.sample new file mode 100644 index 00000000..ff9f37d3 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/RP.sample differ diff --git a/vendor/simple-dns/samples/zonefile/RRSIG.sample b/vendor/simple-dns/samples/zonefile/RRSIG.sample new file mode 100644 index 00000000..076f2c5f Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/RRSIG.sample differ diff --git a/vendor/simple-dns/samples/zonefile/RT.sample b/vendor/simple-dns/samples/zonefile/RT.sample new file mode 100644 index 00000000..2b882f66 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/RT.sample differ diff --git a/vendor/simple-dns/samples/zonefile/SOA.sample b/vendor/simple-dns/samples/zonefile/SOA.sample new file mode 100644 index 00000000..a9e189e1 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/SOA.sample differ diff --git a/vendor/simple-dns/samples/zonefile/SRV.sample b/vendor/simple-dns/samples/zonefile/SRV.sample new file mode 100644 index 00000000..1cb66436 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/SRV.sample differ diff --git a/vendor/simple-dns/samples/zonefile/TXT.sample b/vendor/simple-dns/samples/zonefile/TXT.sample new file mode 100644 index 00000000..3982028e Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/TXT.sample differ diff --git a/vendor/simple-dns/samples/zonefile/WKS.sample b/vendor/simple-dns/samples/zonefile/WKS.sample new file mode 100644 index 00000000..4f2d3fc0 Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/WKS.sample differ diff --git a/vendor/simple-dns/samples/zonefile/ZONEMD.sample b/vendor/simple-dns/samples/zonefile/ZONEMD.sample new file mode 100644 index 00000000..99bacdfb Binary files /dev/null and b/vendor/simple-dns/samples/zonefile/ZONEMD.sample differ diff --git a/vendor/simple-dns/src/bytes_buffer.rs b/vendor/simple-dns/src/bytes_buffer.rs new file mode 100644 index 00000000..9d827602 --- /dev/null +++ b/vendor/simple-dns/src/bytes_buffer.rs @@ -0,0 +1,325 @@ +/// This buffer is used to read bytes from a slice in a safe way. It keeps track of the current +/// position and ensures that the buffer does not read past the end of the slice. +/// +/// `get_*` functions return the value at the current position and advances the buffer position by the +/// size of the value read. +/// +/// `peek_*` functions return the value at the specified offset without advancing the buffer position. +#[derive(Debug, Clone)] +pub struct BytesBuffer<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> BytesBuffer<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { data, offset: 0 } + } + + /// Returns `true` if there are more bytes remaining to read. + pub fn has_remaining(&self) -> bool { + self.offset < self.data.len() + } + + /// advances the buffer position by `length` bytes if there are enough bytes remaining. + pub fn advance(&mut self, length: usize) -> crate::Result<()> { + self.bounds_check(length)?; + self.offset += length; + + Ok(()) + } + + /// Returns a new buffer that with the offset set to the specified position. + /// + /// `position` must be less than the current offset - 2. + pub fn new_at(&self, position: usize) -> crate::Result { + if position >= self.offset - 2 { + return Err(crate::SimpleDnsError::InvalidDnsPacket); + } + + Ok(Self { + data: self.data, + offset: position, + }) + } + + /// Returns a new buffer with the end of the buffer set to the relative `offset` position + /// The current offset is advanced by `offset`. + /// + /// Used when parsing data where the length is not known inside the function receiving the + /// buffer. + pub fn new_limited_to(&mut self, offset: usize) -> crate::Result { + self.bounds_check(offset)?; + + let buffer = Self { + data: &self.data[..self.offset + offset], + offset: self.offset, + }; + self.offset += offset; + + Ok(buffer) + } + + /// Returns a slice of the remaining bytes in the buffer. + pub fn get_remaining(&mut self) -> &'a [u8] { + let value = &self.data[self.offset..]; + self.offset = self.data.len(); + + value + } + + /// Returns a slice of the next `offset` bytes in the buffer. + pub fn get_slice(&mut self, offset: usize) -> crate::Result<&'a [u8]> { + self.bounds_check(offset)?; + + let value = &self.data[self.offset..self.offset + offset]; + self.offset += offset; + + Ok(value) + } + + /// Returns the u32 value at the `offset` position without advancing the offset. + pub fn peek_u32_in(&self, offset: usize) -> crate::Result { + self.peek_array(offset).map(u32::from_be_bytes) + } + + /// Returns the u16 value at the `offset` position without advancing the offset. + pub fn peek_u16_in(&self, offset: usize) -> crate::Result { + self.peek_array(offset).map(u16::from_be_bytes) + } + + /// Returns the u128 value in the current position and advances the offset. + pub fn get_u128(&mut self) -> crate::Result { + self.get_array().map(u128::from_be_bytes) + } + + /// Returns the i32 value in the current position and advances the offset. + pub fn get_i32(&mut self) -> crate::Result { + self.get_array().map(i32::from_be_bytes) + } + + /// Returns the u32 value in the current position and advances the offset. + pub fn get_u32(&mut self) -> crate::Result { + self.get_array().map(u32::from_be_bytes) + } + + /// Returns the u16 value in the current position and advances the offset. + pub fn get_u16(&mut self) -> crate::Result { + self.get_array().map(u16::from_be_bytes) + } + + /// Returns the u8 value in the current position and advances the offset. + pub fn get_u8(&mut self) -> crate::Result { + self.bounds_check(1)?; + + let value = self.data[self.offset]; + self.offset += 1; + + Ok(value) + } + + /// Peek an array of size N at the specified offset without advancing the offset. + fn peek_array(&self, offset: usize) -> crate::Result<[u8; N]> { + self.bounds_check(N + offset)?; + + let offset = self.offset + offset; + let value = self.data[offset..offset + N].try_into().unwrap(); + + Ok(value) + } + + /// Returns an array of size N in the current position and advances the offset. + pub fn get_array(&mut self) -> crate::Result<[u8; N]> { + self.bounds_check(N)?; + + let value = self.data[self.offset..self.offset + N].try_into().unwrap(); + self.offset += N; + + Ok(value) + } + + /// Checks if there are `length` bytes available to read from the current offset. + fn bounds_check(&self, length: usize) -> crate::Result<()> { + if (self.offset + length) <= self.data.len() { + Ok(()) + } else { + Err(crate::SimpleDnsError::InsufficientData) + } + } +} + +impl<'a> From<&'a [u8]> for BytesBuffer<'a> { + fn from(value: &'a [u8]) -> BytesBuffer<'a> { + Self::new(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_has_remaining_returns_true_when_there_are_bytes() { + let buffer = BytesBuffer::new(&[1, 2, 3]); + assert!(buffer.has_remaining()); + } + + #[test] + pub fn test_has_remaining_returns_false_when_there_are_no_bytes() { + let buffer = BytesBuffer::new(&[]); + assert!(!buffer.has_remaining()); + } + + #[test] + pub fn test_advance_advances_the_buffer_position() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.advance(2).unwrap(); + assert_eq!(2, buffer.offset); + } + + #[test] + pub fn test_advance_returns_error_when_advancing_past_end() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + assert!(buffer.advance(4).is_err()); + } + + #[test] + pub fn test_new_at_returns_new_buffer_with_offset() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.advance(3).unwrap(); + let new_buffer = buffer.new_at(0).unwrap(); + assert_eq!(0, new_buffer.offset); + } + + #[test] + pub fn test_new_at_returns_error_when_position_is_greater_than_offset() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.advance(2).unwrap(); + assert!(buffer.new_at(2).is_err()); + } + + #[test] + pub fn test_new_limited_to_returns_new_buffer_with_limited_size() { + let mut buffer = BytesBuffer::new(&[1, 2, 3, 4]); + buffer.advance(1).unwrap(); + let new_buffer = buffer.new_limited_to(2).unwrap(); + + assert_eq!(1, new_buffer.offset); + assert_eq!(3, new_buffer.data.len()); + } + + #[test] + pub fn test_new_limited_to_advances_source_buffer() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.new_limited_to(2).unwrap(); + assert_eq!(2, buffer.offset); + } + + #[test] + pub fn test_new_limited_to_returns_error_when_length_exceeds_remaining() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + assert!(buffer.new_limited_to(4).is_err()); + } + + #[test] + pub fn test_get_remaining_returns_remaining_bytes() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + let remaining = buffer.get_remaining(); + assert_eq!(&[1, 2, 3], remaining); + } + + #[test] + pub fn test_get_remaining_advances_offset_to_end() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.get_remaining(); + assert_eq!(3, buffer.offset); + } + + #[test] + pub fn test_get_remaining_returns_empty_slice_when_no_bytes_remaining() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.advance(3).unwrap(); + assert_eq!(0, buffer.get_remaining().len()); + } + + #[test] + pub fn test_get_slice_returns_slice_of_specified_length() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + let slice = buffer.get_slice(2).unwrap(); + assert_eq!(&[1, 2], slice); + } + + #[test] + pub fn test_get_slice_advances_offset_by_length() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.get_slice(2).unwrap(); + assert_eq!(2, buffer.offset); + } + + #[test] + pub fn test_get_slice_returns_error_when_length_exceeds_remaining() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + assert!(buffer.get_slice(4).is_err()); + } + + #[test] + pub fn test_peek_array_returns_u32_at_offset() { + let buffer = BytesBuffer::new(&[0, 0, 0, 0, 0, 0, 1]); + let value = buffer.peek_array(3).unwrap(); + assert_eq!([0, 0, 0, 1], value); + } + + #[test] + pub fn test_peek_array_returns_error_when_offset_exceeds_remaining() { + let buffer = BytesBuffer::new(&[0, 0, 0, 1, 2, 3]); + assert!(buffer.peek_array::<4>(4).is_err()); + } + + #[test] + pub fn test_peek_array_does_not_advance_buffer() { + let buffer = BytesBuffer::new(&[0, 0, 0, 1, 2, 3]); + buffer.peek_array::<1>(0).unwrap(); + assert_eq!(0, buffer.offset); + } + + #[test] + pub fn test_get_array_returns_an_array_of_bytes() { + let mut buffer = BytesBuffer::new(&[0, 0, 0, 1, 2, 3]); + let value = buffer.get_array(); + assert_eq!(Ok([0, 0, 0, 1]), value); + } + + #[test] + pub fn test_get_array_returns_error_when_length_exceeds_remaining() { + let mut buffer = BytesBuffer::new(&[0, 0, 0]); + let value = buffer.get_array::<4>(); + assert!(value.is_err()); + } + + #[test] + pub fn test_get_array_advances_the_buffer() { + let mut buffer = BytesBuffer::new(&[0, 0, 0, 1, 2, 3]); + buffer.get_array::<4>().unwrap(); + assert_eq!(4, buffer.offset); + } + + #[test] + pub fn test_get_u8_returns_u8_at_offset() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + let value = buffer.get_u8().unwrap(); + assert_eq!(1, value); + } + + #[test] + pub fn test_get_u8_advances_the_offset() { + let mut buffer = BytesBuffer::new(&[1, 2, 3]); + buffer.get_u8().unwrap(); + assert_eq!(1, buffer.offset); + } + + #[test] + pub fn test_get_u8_returns_error_when_no_bytes_remaining() { + let mut buffer = BytesBuffer::new(&[]); + assert!(buffer.get_u8().is_err()); + } +} diff --git a/vendor/simple-dns/src/cursor.rs b/vendor/simple-dns/src/cursor.rs new file mode 100644 index 00000000..bd6fc504 --- /dev/null +++ b/vendor/simple-dns/src/cursor.rs @@ -0,0 +1,157 @@ +#![allow(dead_code)] +#![allow(unused_imports)] +use crate::{ + lib::Vec, + lib::Write, + seek::{Seek, SeekFrom}, +}; + +use core::cmp; + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct Cursor { + inner: T, + pos: u64, +} + +impl Cursor { + pub const fn new(inner: T) -> Cursor { + Cursor { pos: 0, inner } + } + + pub fn into_inner(self) -> T { + self.inner + } + + #[cfg(test)] + pub const fn get_ref(&self) -> &T { + &self.inner + } + + #[cfg(test)] + pub fn set_position(&mut self, pos: u64) { + self.pos = pos; + } +} + +impl Clone for Cursor +where + T: Clone, +{ + #[inline] + fn clone(&self) -> Self { + Cursor { + inner: self.inner.clone(), + pos: self.pos, + } + } + + #[inline] + fn clone_from(&mut self, other: &Self) { + self.inner.clone_from(&other.inner); + self.pos = other.pos; + } +} + +impl Seek for Cursor +where + T: AsRef<[u8]>, +{ + fn seek(&mut self, style: SeekFrom) -> crate::Result { + let (base_pos, offset) = match style { + SeekFrom::Start(n) => { + self.pos = n; + return Ok(n); + } + SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), + SeekFrom::Current(n) => (self.pos, n), + }; + match base_pos.checked_add_signed(offset) { + Some(n) => { + self.pos = n; + Ok(self.pos) + } + None => Err(crate::SimpleDnsError::FailedToWrite), + } + } + + fn stream_position(&mut self) -> crate::Result { + Ok(self.pos) + } +} + +impl Write for Cursor<&mut [u8]> { + fn write(&mut self, buf: &[u8]) -> crate::Result { + slice_write(&mut self.pos, self.inner, buf) + } + + fn flush(&mut self) -> crate::Result<()> { + Ok(()) + } +} + +impl Write for Cursor<[u8; N]> { + fn write(&mut self, buf: &[u8]) -> crate::Result { + slice_write(&mut self.pos, &mut self.inner, buf) + } + + fn flush(&mut self) -> crate::Result<()> { + Ok(()) + } +} + +#[cfg(all(feature = "alloc", not(feature = "std")))] +impl Write for Cursor> { + fn write(&mut self, buf: &[u8]) -> crate::Result { + Ok(vec_write(&mut self.pos, &mut self.inner, buf)) + } + + fn flush(&mut self) -> crate::Result<()> { + Ok(()) + } +} + +#[cfg(all(feature = "alloc", not(feature = "std")))] +impl Write for Cursor<&mut Vec> { + fn write(&mut self, buf: &[u8]) -> crate::Result { + Ok(vec_write(&mut self.pos, self.inner, buf)) + } + + fn flush(&mut self) -> crate::Result<()> { + Ok(()) + } +} + +fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> crate::Result { + let pos = cmp::min(*pos_mut, slice.len() as u64) as usize; + let amt = (&mut slice[pos..]).write(buf)?; + *pos_mut += amt as u64; + Ok(amt) +} + +/// Resizing write implementation for [`Cursor`] +/// +/// Cursor is allowed to have a pre-allocated and initialised +/// vector body, but with a position of 0. This means the [`Write`] +/// will overwrite the contents of the vec. +/// +/// This also allows for the vec body to be empty, but with a position of N. +/// This means that [`Write`] will pad the vec with 0 initially, +/// before writing anything from that point +#[cfg(feature = "alloc")] +fn vec_write(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> usize { + let pos = *pos_mut as usize; + + // Ensure the vector is large enough + let end_pos = pos + buf.len(); + if end_pos > vec.len() { + vec.resize(end_pos, 0); + } + + vec[pos..end_pos].copy_from_slice(buf); + + // Update the position + *pos_mut += buf.len() as u64; + + buf.len() +} diff --git a/vendor/simple-dns/src/dns/character_string.rs b/vendor/simple-dns/src/dns/character_string.rs new file mode 100644 index 00000000..090c0dbb --- /dev/null +++ b/vendor/simple-dns/src/dns/character_string.rs @@ -0,0 +1,161 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::WireFormat, + lib::{ + fmt::{Debug, Display, Formatter}, + Cow, String, ToString, TryFrom, Write, + }, + SimpleDnsError, +}; + +use super::MAX_CHARACTER_STRING_LENGTH; + +/// CharacterString is expressed in one or two ways: +/// - as a contiguous set of characters without interior spaces, +/// - or as a string beginning with a " and ending with a ". +/// +/// Inside a " delimited string any character can occur, except for a " itself, +/// which must be quoted using \ (back slash). +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct CharacterString<'a> { + pub(crate) data: Cow<'a, [u8]>, +} + +impl<'a> CharacterString<'a> { + /// Creates a new validated CharacterString + pub fn new(data: &'a [u8]) -> crate::Result { + Self::internal_new(Cow::Borrowed(data)) + } + + fn internal_new(data: Cow<'a, [u8]>) -> crate::Result { + if data.len() > MAX_CHARACTER_STRING_LENGTH { + return Err(SimpleDnsError::InvalidCharacterString); + } + Ok(Self { data }) + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> CharacterString<'b> { + CharacterString { + data: self.data.into_owned().into(), + } + } +} + +impl<'a> TryFrom> for String { + type Error = crate::SimpleDnsError; + + fn try_from(val: CharacterString<'a>) -> Result { + match String::from_utf8(val.data.into()) { + Ok(s) => Ok(s), + Err(e) => Err(SimpleDnsError::InvalidUtf8String(e)), + } + } +} + +impl<'a> WireFormat<'a> for CharacterString<'a> { + const MINIMUM_LEN: usize = 1; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let length = data.get_u8()? as usize; + if length > MAX_CHARACTER_STRING_LENGTH { + return Err(SimpleDnsError::InvalidCharacterString); + } + + let data = data.get_slice(length)?; + + Ok(Self { + data: Cow::Borrowed(data), + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&[self.data.len() as u8])?; + out.write_all(&self.data) + } + + fn len(&self) -> usize { + self.data.len() + Self::MINIMUM_LEN + } +} + +impl<'a> TryFrom<&'a str> for CharacterString<'a> { + type Error = crate::SimpleDnsError; + + fn try_from(value: &'a str) -> Result { + CharacterString::internal_new(Cow::Borrowed(value.as_bytes())) + } +} + +impl TryFrom for CharacterString<'_> { + type Error = crate::SimpleDnsError; + + fn try_from(value: String) -> Result { + CharacterString::internal_new(Cow::Owned(value.as_bytes().into())) + } +} + +impl Display for CharacterString<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> crate::lib::fmt::Result { + match crate::lib::str::from_utf8(&self.data) { + Ok(s) => f.write_str(s), + Err(_) => { + let s = crate::lib::String::from_utf8_lossy(&self.data); + f.write_str(&s) + } + } + } +} + +impl Debug for CharacterString<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> crate::lib::fmt::Result { + f.debug_struct("CharacterString") + .field("data", &self.to_string()) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::Vec; + + #[test] + fn construct_valid_character_string() { + assert!(CharacterString::new(b"Iamvalid").is_ok()); + assert!(CharacterString::new(br#""I am valid""#).is_ok()); + assert!(CharacterString::new(br#""I am \" also valid""#).is_ok()); + assert!(CharacterString::new(b"I am valid").is_ok()); + + let long_string = [0u8; 300]; + assert!(CharacterString::new(&long_string).is_err()); + } + + #[test] + fn parse() { + let c_string = CharacterString::parse(&mut BytesBuffer::new(b"\x0esome_long_text")); + assert!(c_string.is_ok()); + let c_string = c_string.unwrap(); + assert_eq!(15, c_string.len()); + assert_eq!("some_long_text", c_string.to_string()); + } + + #[test] + fn append_to_vec() { + let mut out = Vec::new(); + let c_string = CharacterString::new("some_long_text".as_bytes()).unwrap(); + c_string.write_to(&mut out).unwrap(); + + assert_eq!(b"\x0esome_long_text", &out[..]); + assert_eq!(b"\x0esome_long_text", &out[..]); + } + + #[test] + fn panic_display() { + let c = CharacterString::new(&[0xFF]).expect("failed to create character string"); + assert_eq!(c.to_string(), "\u{FFFD}"); + } +} diff --git a/vendor/simple-dns/src/dns/header.rs b/vendor/simple-dns/src/dns/header.rs new file mode 100644 index 00000000..e5192279 --- /dev/null +++ b/vendor/simple-dns/src/dns/header.rs @@ -0,0 +1,233 @@ +use crate::{bytes_buffer::BytesBuffer, lib::Write, rdata::OPT, ResourceRecord}; + +use super::{PacketFlag, OPCODE, RCODE}; + +pub(crate) mod masks { + pub const OPCODE_MASK: u16 = 0b0111_1000_0000_0000; + pub const RESERVED_MASK: u16 = 0b0000_0000_0100_0000; + pub const RESPONSE_CODE_MASK: u16 = 0b0000_0000_0000_1111; +} +/// Contains general information about the packet +#[derive(Debug, Clone)] +pub(crate) struct Header<'a> { + /// The identification of the packet, must be defined when querying + pub id: u16, + /// Indicates the type of query in this packet + pub opcode: OPCODE, + /// [RCODE](`RCODE`) indicates the response code for this packet + pub response_code: RCODE, + + pub z_flags: PacketFlag, + + pub opt: Option>, + + pub questions: u16, + pub answers: u16, + pub name_servers: u16, + pub additional_records: u16, +} + +impl<'a> Header<'a> { + /// Creates a new header for a query packet + pub fn new_query(id: u16) -> Self { + Self { + id, + opcode: OPCODE::StandardQuery, + response_code: RCODE::NoError, + z_flags: PacketFlag::empty(), + opt: None, + + questions: 0, + answers: 0, + name_servers: 0, + additional_records: 0, + } + } + + /// Creates a new header for a reply packet + pub fn new_reply(id: u16, opcode: OPCODE) -> Self { + Self { + id, + opcode, + response_code: RCODE::NoError, + z_flags: PacketFlag::RESPONSE, + opt: None, + + questions: 0, + answers: 0, + name_servers: 0, + additional_records: 0, + } + } + + pub fn set_flags(&mut self, flags: PacketFlag) { + self.z_flags |= flags; + } + + pub fn remove_flags(&mut self, flags: PacketFlag) { + self.z_flags.remove(flags); + } + + pub fn has_flags(&self, flags: PacketFlag) -> bool { + self.z_flags.contains(flags) + } + + /// Parse a slice of 12 bytes into a Packet header + pub fn parse(data: &mut BytesBuffer) -> crate::Result { + let id = data.get_u16()?; + let flags = data.get_u16()?; + if flags & masks::RESERVED_MASK != 0 { + return Err(crate::SimpleDnsError::InvalidHeaderData); + } + + let header = Self { + id, + opcode: ((flags & masks::OPCODE_MASK) >> masks::OPCODE_MASK.trailing_zeros()).into(), + response_code: (flags & masks::RESPONSE_CODE_MASK).into(), + z_flags: PacketFlag::from_bits_truncate(flags), + opt: None, + + questions: data.get_u16()?, + answers: data.get_u16()?, + name_servers: data.get_u16()?, + additional_records: data.get_u16()?, + }; + + Ok(header) + } + + /// Writes this header to a buffer of 12 bytes + pub fn write_to( + &self, + buffer: &mut T, + questions: u16, + answers: u16, + name_servers: u16, + additional_records: u16, + ) -> crate::Result<()> { + buffer.write_all(&self.id.to_be_bytes())?; + buffer.write_all(&self.get_flags().to_be_bytes())?; + buffer.write_all(&questions.to_be_bytes())?; + buffer.write_all(&answers.to_be_bytes())?; + buffer.write_all(&name_servers.to_be_bytes())?; + buffer.write_all(&additional_records.to_be_bytes())?; + + Ok(()) + } + + fn get_flags(&self) -> u16 { + let mut flags = self.z_flags.bits(); + + flags |= (self.opcode as u16) << masks::OPCODE_MASK.trailing_zeros(); + flags |= self.response_code as u16 & masks::RESPONSE_CODE_MASK; + + flags + } + + pub(crate) fn opt_rr(&self) -> Option> { + self.opt.as_ref().map(|opt| { + ResourceRecord::new( + [].into(), + crate::CLASS::IN, + opt.encode_ttl(self), + crate::rdata::RData::OPT(opt.clone()), + ) + }) + } + + pub(crate) fn extract_info_from_opt_rr(&mut self, opt_rr: Option>) { + if let Some(opt) = opt_rr { + self.response_code = OPT::extract_rcode_from_ttl(opt.ttl, self); + self.opt = match opt.rdata { + crate::rdata::RData::OPT(opt) => Some(opt), + _ => unreachable!(), + }; + } + } +} + +#[cfg(test)] +mod tests { + use crate::header_buffer; + use crate::lib::vec; + + use super::*; + + #[test] + fn write_example_query() { + let mut header = Header::new_query(u16::MAX); + + header.set_flags(PacketFlag::TRUNCATION | PacketFlag::RECURSION_DESIRED); + + let mut buf = vec![]; + header.write_to(&mut buf, 0, 0, 0, 0).unwrap(); + + assert_eq!( + b"\xff\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00", + &buf[..] + ); + } + + #[test] + fn parse_example_query() { + let buffer = b"\xff\xff\x03\x00\x00\x02\x00\x02\x00\x02\x00\x02"; + let header = Header::parse(&mut buffer[..].into()).unwrap(); + + assert_eq!(u16::MAX, header.id); + assert_eq!(OPCODE::StandardQuery, header.opcode); + assert!(!header.has_flags( + PacketFlag::AUTHORITATIVE_ANSWER + | PacketFlag::RECURSION_AVAILABLE + | PacketFlag::RESPONSE + )); + assert!(header.has_flags(PacketFlag::TRUNCATION | PacketFlag::RECURSION_DESIRED)); + assert_eq!(RCODE::NoError, header.response_code); + assert_eq!(2, header_buffer::additional_records(&buffer[..]).unwrap()); + assert_eq!(2, header_buffer::answers(&buffer[..]).unwrap()); + assert_eq!(2, header_buffer::name_servers(&buffer[..]).unwrap()); + assert_eq!(2, header_buffer::questions(&buffer[..]).unwrap()); + } + + #[test] + fn read_write_questions_count() { + let mut buffer = [0u8; 12]; + header_buffer::set_questions(&mut buffer, 1); + assert_eq!(1, header_buffer::questions(&buffer).unwrap()); + } + + #[test] + fn read_write_answers_count() { + let mut buffer = [0u8; 12]; + header_buffer::set_answers(&mut buffer, 1); + assert_eq!(1, header_buffer::answers(&buffer).unwrap()); + } + + #[test] + fn read_write_name_servers_count() { + let mut buffer = [0u8; 12]; + header_buffer::set_name_servers(&mut buffer, 1); + assert_eq!(1, header_buffer::name_servers(&buffer).unwrap()); + } + + #[test] + fn read_write_additional_records_count() { + let mut buffer = [0u8; 12]; + header_buffer::set_additional_records(&mut buffer, 1); + assert_eq!(1, header_buffer::additional_records(&buffer).unwrap()); + } + + #[test] + fn big_rcode_doesnt_break_header() { + let mut header = Header::new_reply(1, OPCODE::StandardQuery); + header.response_code = RCODE::BADVERS; + + let mut buffer = vec![]; + header.write_to(&mut buffer, 0, 0, 0, 0).unwrap(); + + assert_ne!(RCODE::BADVERS, header_buffer::rcode(&buffer[..]).unwrap()); + + let header = Header::parse(&mut buffer[..].into()).expect("Header parsing failed"); + assert_eq!(RCODE::NoError, header.response_code); + assert!(header.has_flags(PacketFlag::RESPONSE)); + } +} diff --git a/vendor/simple-dns/src/dns/header_buffer.rs b/vendor/simple-dns/src/dns/header_buffer.rs new file mode 100644 index 00000000..951c633f --- /dev/null +++ b/vendor/simple-dns/src/dns/header_buffer.rs @@ -0,0 +1,292 @@ +//! Helper functions to assert a buffer for the header flags of a DNS Packet. Useful for checking the header +//! without parsing the whole packet. +//! WARNING: Flags and RCODE information may be incomplete if the packet contains EDNS (OPT) or +//! DNSSEC Resource Records +//! +//! ```rust +//! use simple_dns::{header_buffer, PacketFlag}; +//! +//! let buffer = b"\xff\xff\x03\x00\x00\x02\x00\x02\x00\x02\x00\x02"; +//! assert_eq!(u16::MAX, header_buffer::id(&buffer[..]).unwrap()); +//! assert!(!header_buffer::has_flags(&buffer[..], PacketFlag::RESPONSE).unwrap()); +//! ``` + +use crate::{PacketFlag, OPCODE, RCODE}; + +use super::header::masks; + +/// Returns the packet id from the header buffer +pub fn id(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(id_unchecked) +} + +/// Returns the packet id from the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 2` +pub fn id_unchecked(buffer: &[u8]) -> u16 { + u16::from_be_bytes(buffer[..2].try_into().unwrap()) +} + +/// Returns the questions count from the header buffer +pub fn questions(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(questions_unchecked) +} + +/// Returns the questions count from the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 6` +pub fn questions_unchecked(buffer: &[u8]) -> u16 { + u16::from_be_bytes(buffer[4..6].try_into().unwrap()) +} + +#[cfg(test)] +/// Writes the questions count in the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 6` +pub(crate) fn set_questions(buffer: &mut [u8], question_count: u16) { + buffer[4..6].copy_from_slice(&question_count.to_be_bytes()); +} + +/// Returns the answers count from the header buffer +pub fn answers(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(answers_unchecked) +} + +/// Returns the answers count from the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 8` +pub fn answers_unchecked(buffer: &[u8]) -> u16 { + u16::from_be_bytes(buffer[6..8].try_into().unwrap()) +} + +#[cfg(test)] +/// Writes the answers count in the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 8` +pub(crate) fn set_answers(buffer: &mut [u8], answers_count: u16) { + buffer[6..8].copy_from_slice(&answers_count.to_be_bytes()); +} + +/// Returns the name servers count from the header buffer +pub fn name_servers(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(name_servers_unchecked) +} + +/// Returns the name servers count from the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 10` +pub fn name_servers_unchecked(buffer: &[u8]) -> u16 { + u16::from_be_bytes(buffer[8..10].try_into().unwrap()) +} + +#[cfg(test)] +/// Writes the name servers count in the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 10` +pub(crate) fn set_name_servers(buffer: &mut [u8], name_servers_count: u16) { + buffer[8..10].copy_from_slice(&name_servers_count.to_be_bytes()); +} + +/// Returns the additional records from the header buffer +pub fn additional_records(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(additional_records_unchecked) +} + +/// Returns the additional records from the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 12` +pub fn additional_records_unchecked(buffer: &[u8]) -> u16 { + u16::from_be_bytes(buffer[10..12].try_into().unwrap()) +} + +#[cfg(test)] +/// Writes the additional records count in the header buffer +/// +/// # Panics +/// Panics if `buffer.len() < 12` +pub(crate) fn set_additional_records(buffer: &mut [u8], additional_records_count: u16) { + buffer[10..12].copy_from_slice(&additional_records_count.to_be_bytes()); +} + +/// Verify if buffer has the flags set. +/// WARNING: This information may be wrong if there is an OPT record in packet +pub fn has_flags(buffer: &[u8], flags: PacketFlag) -> crate::Result { + check_buffer_len(buffer).map(|_| has_flags_unchecked(buffer, flags)) +} + +/// Verify if buffer has the flags set. +/// WARNING: This information may be wrong if there is an OPT record in packet +/// +/// # Panics +/// Panics if `buffer.len() < 12` +pub fn has_flags_unchecked(buffer: &[u8], flags: PacketFlag) -> bool { + let bits = u16::from_be_bytes(buffer[2..4].try_into().unwrap()); + PacketFlag::from_bits_truncate(bits).contains(flags) +} + +/// Get the RCODE from the buffer. +/// WARNING: This information may be wrong if there is an OPT record in packet +pub fn rcode(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(rcode_unchecked) +} + +/// Get the RCODE from the buffer. +/// WARNING: This information may be wrong if there is an OPT record in packet +/// +/// # Panics +/// Panics if `buffer.len() < 12` +pub fn rcode_unchecked(buffer: &[u8]) -> RCODE { + let flags = u16::from_be_bytes(buffer[2..4].try_into().unwrap()); + (flags & masks::RESPONSE_CODE_MASK).into() +} + +/// Get the OPCODE from the buffer +pub fn opcode(buffer: &[u8]) -> crate::Result { + check_buffer_len(buffer).map(opcode_unchecked) +} + +/// Get the OPCODE from the buffer +/// +/// # Panics +/// Panics if `buffer.len() < 12` +pub fn opcode_unchecked(buffer: &[u8]) -> OPCODE { + let flags = u16::from_be_bytes(buffer[2..4].try_into().unwrap()); + ((flags & masks::OPCODE_MASK) >> masks::OPCODE_MASK.trailing_zeros()).into() +} + +fn check_buffer_len(buffer: &[u8]) -> crate::Result<&[u8]> { + if buffer.len() < 12 { + Err(crate::SimpleDnsError::InvalidHeaderData) + } else { + Ok(buffer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_write_questions_count() { + let mut buffer = [0u8; 12]; + set_questions(&mut buffer, 1); + assert_eq!(1, questions(&buffer).unwrap()); + } + + #[test] + fn read_write_answers_count() { + let mut buffer = [0u8; 12]; + set_answers(&mut buffer, 1); + assert_eq!(1, answers(&buffer).unwrap()); + } + + #[test] + fn read_write_name_servers_count() { + let mut buffer = [0u8; 12]; + set_name_servers(&mut buffer, 1); + assert_eq!(1, name_servers(&buffer).unwrap()); + } + + #[test] + fn read_write_additional_records_count() { + let mut buffer = [0u8; 12]; + set_additional_records(&mut buffer, 1); + assert_eq!(1, additional_records(&buffer).unwrap()); + } + + #[test] + fn id_returns_error_for_short_buffer() { + assert!(id(&[0u8; 11]).is_err()); + } + + #[test] + fn questions_returns_error_for_short_buffer() { + assert!(questions(&[0u8; 11]).is_err()); + } + + #[test] + fn answers_returns_error_for_short_buffer() { + assert!(answers(&[0u8; 11]).is_err()); + } + + #[test] + fn name_servers_returns_error_for_short_buffer() { + assert!(name_servers(&[0u8; 11]).is_err()); + } + + #[test] + fn additional_records_returns_error_for_short_buffer() { + assert!(additional_records(&[0u8; 11]).is_err()); + } + + #[test] + fn has_flags_returns_error_for_short_buffer() { + assert!(has_flags(&[0u8; 11], PacketFlag::RESPONSE).is_err()); + } + + #[test] + fn rcode_returns_error_for_short_buffer() { + assert!(rcode(&[0u8; 11]).is_err()); + } + + #[test] + fn opcode_returns_error_for_short_buffer() { + assert!(opcode(&[0u8; 11]).is_err()); + } + + #[test] + #[should_panic] + fn id_unchecked_panics_for_short_buffer() { + id_unchecked(&[]); + } + + #[test] + #[should_panic] + fn questions_unchecked_panics_for_short_buffer() { + questions_unchecked(&[0u8; 4]); + } + + #[test] + #[should_panic] + fn answers_unchecked_panics_for_short_buffer() { + answers_unchecked(&[0u8; 6]); + } + + #[test] + #[should_panic] + fn name_servers_unchecked_panics_for_short_buffer() { + name_servers_unchecked(&[0u8; 8]); + } + + #[test] + #[should_panic] + fn additional_records_unchecked_panics_for_short_buffer() { + additional_records_unchecked(&[0u8; 10]); + } + + #[test] + #[should_panic] + fn has_flags_unchecked_panics_for_short_buffer() { + has_flags_unchecked(&[0u8; 1], PacketFlag::RESPONSE); + } + + #[test] + #[should_panic] + fn rcode_unchecked_panics_for_short_buffer() { + rcode_unchecked(&[0u8; 1]); + } + + #[test] + #[should_panic] + fn opcode_unchecked_panics_for_short_buffer() { + opcode_unchecked(&[0u8; 1]); + } +} diff --git a/vendor/simple-dns/src/dns/mod.rs b/vendor/simple-dns/src/dns/mod.rs new file mode 100644 index 00000000..1053c679 --- /dev/null +++ b/vendor/simple-dns/src/dns/mod.rs @@ -0,0 +1,286 @@ +//! Provides parsing and manipulation for DNS packets + +mod character_string; +pub use character_string::CharacterString; + +mod name; +pub use name::{Label, Name}; + +mod packet; +pub use packet::Packet; + +mod header; +use header::Header; + +pub mod header_buffer; + +mod wire_format; +pub(crate) use wire_format::WireFormat; + +mod question; +pub use question::Question; + +pub mod rdata; +pub use rdata::TYPE; + +mod resource_record; +pub use resource_record::ResourceRecord; + +use crate::lib::TryFrom; +use bitflags::bitflags; + +const MAX_LABEL_LENGTH: usize = 63; +const MAX_NAME_LENGTH: usize = 255; +const MAX_CHARACTER_STRING_LENGTH: usize = 255; +const MAX_NULL_LENGTH: usize = 65535; + +bitflags! { + /// Possible Packet Flags + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + pub struct PacketFlag: u16 { + /// Indicates if this packet is a query or a response. This is the QR flag in the DNS + /// specifications, this flag is called Response here to be more ergonomic + const RESPONSE = 0b1000_0000_0000_0000; + + /// Authoritative Answer - this bit is valid in responses, + /// and specifies that the responding name server is an authority for the domain name in question section. + const AUTHORITATIVE_ANSWER = 0b0000_0100_0000_0000; + /// TrunCation - specifies that this message was truncated due to + /// length greater than that permitted on the transmission channel. + const TRUNCATION = 0b0000_0010_0000_0000; + /// Recursion Desired may be set in a query and is copied into the response. + /// If RD is set, it directs the name server to pursue the query recursively. + /// Recursive query support is optional. + const RECURSION_DESIRED = 0b0000_0001_0000_0000; + /// Recursion Available is set or cleared in a response. + /// It denotes whether recursive query support is available in the name server. + const RECURSION_AVAILABLE = 0b0000_0000_1000_0000; + #[allow(missing_docs)] + const AUTHENTIC_DATA = 0b0000_0000_0010_0000; + #[allow(missing_docs)] + const CHECKING_DISABLED = 0b0000_0000_0001_0000; + } +} + +// /// The maximum DNS packet size is 9000 bytes less the maximum +// /// sizes of the IP (60) and UDP (8) headers. +// // const MAX_PACKET_SIZE: usize = 9000 - 68; + +/// Possible QTYPE values for a Question in a DNS packet +/// Each value is described according to its own RFC +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum QTYPE { + /// Query for the specific [TYPE] + TYPE(TYPE), + /// A request for incremental transfer of a zone. [RFC 1995](https://tools.ietf.org/html/rfc1995) + IXFR, + /// A request for a transfer of an entire zone, [RFC 1035](https://tools.ietf.org/html/rfc1035) + AXFR, + /// A request for mailbox-related records (MB, MG or MR), [RFC 1035](https://tools.ietf.org/html/rfc1035) + MAILB, + /// A request for mail agent RRs (Obsolete - see MX), [RFC 1035](https://tools.ietf.org/html/rfc1035) + MAILA, + /// A request for all records, [RFC 1035](https://tools.ietf.org/html/rfc1035) + ANY, +} + +impl From for QTYPE { + fn from(v: TYPE) -> Self { + Self::TYPE(v) + } +} + +impl TryFrom for QTYPE { + type Error = crate::SimpleDnsError; + + fn try_from(value: u16) -> Result { + match value { + 251 => Ok(QTYPE::IXFR), + 252 => Ok(QTYPE::AXFR), + 253 => Ok(QTYPE::MAILB), + 254 => Ok(QTYPE::MAILA), + 255 => Ok(QTYPE::ANY), + v => match TYPE::from(v) { + TYPE::Unknown(_) => Err(Self::Error::InvalidQType(v)), + ty => Ok(ty.into()), + }, + } + } +} + +impl From for u16 { + fn from(val: QTYPE) -> Self { + match val { + QTYPE::TYPE(ty) => ty.into(), + QTYPE::IXFR => 251, + QTYPE::AXFR => 252, + QTYPE::MAILB => 253, + QTYPE::MAILA => 254, + QTYPE::ANY => 255, + } + } +} + +/// Possible CLASS values for a Resource in a DNS packet +/// Each value is described according to its own RFC +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum CLASS { + /// The Internet, [RFC 1035](https://tools.ietf.org/html/rfc1035) + IN = 1, + /// The CSNET class (Obsolete - used only for examples in some obsolete RFCs), [RFC 1035](https://tools.ietf.org/html/rfc1035) + CS = 2, + /// The CHAOS class, [RFC 1035](https://tools.ietf.org/html/rfc1035) + CH = 3, + /// Hesiod [Dyer 87], [RFC 1035](https://tools.ietf.org/html/rfc1035) + HS = 4, + /// [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + NONE = 254, +} + +impl TryFrom for CLASS { + type Error = crate::SimpleDnsError; + + fn try_from(value: u16) -> Result { + use self::CLASS::*; + match value { + 1 => Ok(IN), + 2 => Ok(CS), + 3 => Ok(CH), + 4 => Ok(HS), + 254 => Ok(NONE), + v => Err(Self::Error::InvalidClass(v)), + } + } +} + +/// Possible QCLASS values for a Question in a DNS packet +/// Each value is described according to its own RFC +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum QCLASS { + /// Query for the specific [CLASS] + CLASS(CLASS), + /// [RFC 1035](https://tools.ietf.org/html/rfc1035) + ANY, +} + +impl From for QCLASS { + fn from(v: CLASS) -> Self { + Self::CLASS(v) + } +} + +impl TryFrom for QCLASS { + type Error = crate::SimpleDnsError; + + fn try_from(value: u16) -> Result { + match value { + 255 => Ok(QCLASS::ANY), + v => CLASS::try_from(v).map(|x| x.into()), + } + } +} + +impl From for u16 { + fn from(val: QCLASS) -> Self { + match val { + QCLASS::CLASS(class) => class as u16, + QCLASS::ANY => 255, + } + } +} + +/// Possible OPCODE values for a DNS packet, use to specify the type of operation. +/// [RFC 1035](https://tools.ietf.org/html/rfc1035): A four bit field that specifies kind of query in this message. +/// This value is set by the originator of a query and copied into the response. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum OPCODE { + /// Normal query + StandardQuery = 0, + /// Inverse query (query a name by IP) + InverseQuery = 1, + /// Server status request + ServerStatusRequest = 2, + /// Notify query + Notify = 4, + /// Update query [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + Update = 5, + /// Reserved opcode for future use + Reserved, +} + +impl From for OPCODE { + fn from(code: u16) -> Self { + match code { + 0 => OPCODE::StandardQuery, + 1 => OPCODE::InverseQuery, + 2 => OPCODE::ServerStatusRequest, + 4 => OPCODE::Notify, + 5 => OPCODE::Update, + _ => OPCODE::Reserved, + } + } +} + +/// Possible RCODE values for a DNS packet +/// [RFC 1035](https://tools.ietf.org/html/rfc1035) Response code - this 4 bit field is set as part of responses. +/// The values have the following interpretation +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RCODE { + /// No error condition + NoError = 0, + /// Format error - The name server was unable to interpret the query. + FormatError = 1, + /// Server failure - The name server was unable to process this query due to a problem with the name server. + ServerFailure = 2, + /// Name Error - Meaningful only for responses from an authoritative name server, + /// this code signifies that the domain name referenced in the query does not exist. + NameError = 3, + /// Not Implemented - The name server does not support the requested kind of query. + NotImplemented = 4, + /// Refused - The name server refuses to perform the specified operation for policy reasons. + /// For example, a name server may not wish to provide the information to the particular requester, + /// or a name server may not wish to perform a particular operation (e.g., zone transfer) for particular data. + Refused = 5, + /// Some name that ought not to exist, does exist. + /// [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + YXDOMAIN = 6, + /// Some RRset that ought not to exist, does exist. + /// [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + YXRRSET = 7, + /// Some RRset that ought to exist, does not exist. + /// [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + NXRRSET = 8, + /// The server is not authoritative for the zone named in the Zone Section. + /// [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + NOTAUTH = 9, + /// A name used in the Prerequisite or Update Section is not within the zone denoted by the Zone Section. + /// [RFC 2136](https://datatracker.ietf.org/doc/html/rfc2136) + NOTZONE = 10, + /// EDNS Version not supported by the responder + /// [RFC 6891](https://datatracker.ietf.org/doc/html/rfc6891) + BADVERS = 16, + + /// Reserved for future use. + Reserved, +} + +impl From for RCODE { + fn from(code: u16) -> Self { + use RCODE::*; + match code { + 0 => NoError, + 1 => FormatError, + 2 => ServerFailure, + 3 => NameError, + 4 => NotImplemented, + 5 => Refused, + 6 => YXDOMAIN, + 7 => YXRRSET, + 8 => NXRRSET, + 9 => NOTAUTH, + 10 => NOTZONE, + 16 => BADVERS, + _ => RCODE::Reserved, + } + } +} diff --git a/vendor/simple-dns/src/dns/name.rs b/vendor/simple-dns/src/dns/name.rs new file mode 100644 index 00000000..eb5c7f51 --- /dev/null +++ b/vendor/simple-dns/src/dns/name.rs @@ -0,0 +1,797 @@ +use crate::{ + bytes_buffer::BytesBuffer, + lib::{ + fmt::{Debug, Display, Formatter, Result as FmtResult}, + format, Cow, Hash, Hasher, Iter, Seek, String, ToString, TryFrom, Vec, Write, + }, +}; + +use super::{WireFormat, MAX_LABEL_LENGTH, MAX_NAME_LENGTH}; + +const POINTER_MASK: u8 = 0b1100_0000; +const POINTER_MASK_U16: u16 = 0b1100_0000_0000_0000; +const MAX_COMPRESSION_OFFSET: u64 = !POINTER_MASK_U16 as u64; + +// NOTE: there are no extend labels implemented today +// const EXTENDED_LABEL: u8 = 0b0100_0000; +// const EXTENDED_LABEL_U16: u16 = 0b0100_0000_0000_0000; + +/// A Name represents a domain-name, which consists of character strings separated by dots. +/// Each section of a name is called label +/// ex: `google.com` consists of two labels `google` and `com` +/// +/// A valid name contains only alphanumeric characters, hyphen (-), underscore (_) or dots (.) and must not exceed 255 characters. +/// Each label must not exceed 63 characters. +/// +/// Microsoft implementation allows unicode characters in the name content. +/// To create a name with unicode characters, use [`Name::new_unchecked`] or +/// [`Name::new_with_labels`] +#[derive(Eq, Clone)] +pub struct Name<'a> { + labels: Vec>, +} + +impl<'a> Name<'a> { + /// Creates a new Name. Returns [`Result::::Ok`] if given `name` contents are valid. + pub fn new(name: &'a str) -> crate::Result { + let labels = LabelsIter::new(name.as_bytes()) + .map(Label::new) + .collect::, _>>()?; + + let name = Self { labels }; + + if name.len() > MAX_NAME_LENGTH { + Err(crate::SimpleDnsError::InvalidServiceName) + } else { + Ok(name) + } + } + + /// Create a new Name without checking for size limits or contents + pub fn new_unchecked(name: &'a str) -> Self { + let labels = LabelsIter::new(name.as_bytes()) + .map(Label::new_unchecked) + .collect(); + + Self { labels } + } + + /// Creates a new Name with given labels + /// + /// Allows construction of labels with `.` in them. + pub fn new_with_labels(labels: &[Label<'a>]) -> Self { + Self { + labels: labels.to_vec(), + } + } + + /// Verify if name ends with .local. + pub fn is_link_local(&self) -> bool { + match self.iter().last() { + Some(label) => b"local".eq_ignore_ascii_case(&label.data), + None => false, + } + } + + /// Returns an Iter of this Name Labels + pub fn iter(&'a self) -> Iter<'a, Label<'a>> { + self.labels.iter() + } + + /// Returns true if self is a subdomain of other + pub fn is_subdomain_of(&self, other: &Name) -> bool { + self.labels.len() > other.labels.len() + && other + .iter() + .rev() + .zip(self.iter().rev()) + .all(|(o, s)| *o == *s) + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> Name<'b> { + Name { + labels: self.labels.into_iter().map(|l| l.into_owned()).collect(), + } + } + + /// Returns the subdomain part of self, based on `domain`. + /// If self is not a subdomain of `domain`, returns None + /// + /// Example: + /// ``` + /// # use simple_dns::Name; + /// let name = Name::new_unchecked("sub.domain.local"); + /// let domain = Name::new_unchecked("domain.local"); + /// + /// assert!(domain.without(&name).is_none()); + /// + /// let sub = name.without(&domain).unwrap(); + /// assert_eq!(sub.to_string(), "sub") + /// ``` + pub fn without(&'_ self, domain: &Name) -> Option> { + if self.is_subdomain_of(domain) { + let labels = self.labels[..self.labels.len() - domain.labels.len()].to_vec(); + + Some(Name { labels }) + } else { + None + } + } + + /// Get the labels that compose this name + pub fn get_labels(&'_ self) -> &'_ [Label<'a>] { + &self.labels[..] + } + + fn plain_append(&self, out: &mut T) -> crate::Result<()> { + for label in self.iter() { + out.write_all(&[label.len() as u8])?; + out.write_all(&label.data)?; + } + + out.write_all(&[0])?; + Ok(()) + } + + fn compress_append( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[Label<'a>], u16>, + ) -> crate::Result<()> { + for (i, label) in self.iter().enumerate() { + match name_refs.entry(&self.labels[i..]) { + crate::lib::BTreeEntry::Occupied(e) => { + let p = *e.get(); + out.write_all(&(p | POINTER_MASK_U16).to_be_bytes())?; + + return Ok(()); + } + crate::lib::BTreeEntry::Vacant(e) => { + let pos = out.stream_position()?; + if pos <= MAX_COMPRESSION_OFFSET { + e.insert(pos as u16); + } + out.write_all(&[label.len() as u8])?; + out.write_all(&label.data)?; + } + } + } + + out.write_all(&[0])?; + Ok(()) + } + + /// Returns `true` if the name is valid. + pub fn is_valid(&self) -> bool { + self.labels.iter().all(|label| label.is_valid()) + } + + /// Returns the bytes of each of the labels that compose this Name + pub fn as_bytes(&self) -> impl Iterator { + self.labels.iter().map(|label| label.as_ref()) + } +} + +impl<'a> WireFormat<'a> for Name<'a> { + const MINIMUM_LEN: usize = 1; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + // Parse labels will extract labels until it finds a 0 len label + // or a pointer to another label + fn parse_labels<'a>( + data: &mut BytesBuffer<'a>, + name_len: &mut usize, + labels: &mut Vec>, + ) -> crate::Result> { + loop { + match data.get_u8()? { + 0 => break Ok(None), + len if len & POINTER_MASK == POINTER_MASK => { + let mut pointer = len as u16; + pointer <<= 8; + pointer += data.get_u8()? as u16; + pointer &= !POINTER_MASK_U16; + + break Ok(Some(pointer as usize)); + } + len => { + *name_len += 1 + len as usize; + + // Checking the full name len to avoid circular pointers + if *name_len >= MAX_NAME_LENGTH { + return Err(crate::SimpleDnsError::InvalidDnsPacket); + } + + if len as usize > MAX_LABEL_LENGTH { + return Err(crate::SimpleDnsError::InvalidServiceLabel); + } + + // Parsing allow invalid characters in the label. + // However, the length of the label must be validated (above) + labels.push(Label::new_unchecked(data.get_slice(len as usize)?)); + } + } + } + } + + let mut labels = Vec::new(); + let mut name_len = 0usize; + + let mut pointer = parse_labels(data, &mut name_len, &mut labels)?; + + let mut data = data.clone(); + while let Some(p) = pointer { + // By creating a new buffer, it is possible to simplify the parse routine since + // the original buffer position will remain intact when iterating throught the + // pointers + data = data.new_at(p)?; + pointer = parse_labels(&mut data, &mut name_len, &mut labels)?; + } + + Ok(Self { labels }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.plain_append(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[Label<'a>], u16>, + ) -> crate::Result<()> { + self.compress_append(out, name_refs) + } + + fn len(&self) -> usize { + self.labels + .iter() + .map(|label| label.len() + 1) + .sum::() + + Self::MINIMUM_LEN + } +} + +impl<'a> TryFrom<&'a str> for Name<'a> { + type Error = crate::SimpleDnsError; + + fn try_from(value: &'a str) -> Result { + Name::new(value) + } +} + +impl<'a> From<&'a [Label<'a>]> for Name<'a> { + fn from(labels: &'a [Label<'a>]) -> Self { + Name::new_with_labels(labels) + } +} + +impl<'a, const N: usize> From<[Label<'a>; N]> for Name<'a> { + fn from(labels: [Label<'a>; N]) -> Self { + Name::new_with_labels(&labels) + } +} + +impl Display for Name<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + let mut labels = self.labels.iter(); + + if let Some(label) = labels.next() { + f.write_fmt(format_args!("{label}"))?; + } + + for label in labels { + f.write_fmt(format_args!(".{label}"))?; + } + + Ok(()) + } +} + +impl Debug for Name<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_tuple("Name") + .field(&format!("{self}")) + .field(&format!("{}", self.len())) + .finish() + } +} + +impl PartialEq for Name<'_> { + fn eq(&self, other: &Self) -> bool { + self.labels == other.labels + } +} + +impl Hash for Name<'_> { + fn hash(&self, state: &mut H) { + self.labels.hash(state); + } +} + +/// An iterator over the labels in a domain name +struct LabelsIter<'a> { + bytes: &'a [u8], + current: usize, +} + +impl<'a> LabelsIter<'a> { + fn new(bytes: &'a [u8]) -> Self { + Self { bytes, current: 0 } + } +} + +impl<'a> Iterator for LabelsIter<'a> { + type Item = Cow<'a, [u8]>; + + fn next(&mut self) -> Option { + for i in self.current..self.bytes.len() { + if self.bytes[i] == b'.' { + let current = crate::lib::mem::replace(&mut self.current, i + 1); + if i - current == 0 { + continue; + } + return Some(self.bytes[current..i].into()); + } + } + + if self.current < self.bytes.len() { + let current = crate::lib::mem::replace(&mut self.current, self.bytes.len()); + Some(self.bytes[current..].into()) + } else { + None + } + } +} + +/// Represents a label in a domain name +/// +/// A valid label is consists of A-Z, a-z, 0-9, and hyphen (-), and must be at most 63 characters +/// in length. +/// This library also considers valid any label starting with underscore (_), to be able to parse mDNS domain names. +/// +/// Microsoft implementation allows unicode characters in the label content. +/// To create a label with unicode characters, use [`Label::new_unchecked`] +/// +/// The `Display` implementation uses [`std::string::String::from_utf8_lossy`] to display the +/// label. +#[derive(Eq, PartialEq, Hash, Clone, PartialOrd, Ord)] +pub struct Label<'a> { + data: Cow<'a, [u8]>, +} + +impl<'a> Label<'a> { + /// Create a new [`Label`] if given data is valid and within the limits + pub fn new>>(data: T) -> crate::Result { + let label = Self::new_unchecked(data); + if !label.is_valid() { + return Err(crate::SimpleDnsError::InvalidServiceLabel); + } + + Ok(label) + } + + /// Create a new Label without checking for size limits or valid content. + /// This function can be used to create labels with unicode characters + pub fn new_unchecked>>(data: T) -> Self { + Self { data: data.into() } + } + + /// Returns the length of the label + pub fn len(&self) -> usize { + self.data.len() + } + + /// Returns true if the label is empty + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> Label<'b> { + Label { + data: self.data.into_owned().into(), + } + } + + /// Returns `true` if the label is valid. + pub fn is_valid(&self) -> bool { + if self.data.is_empty() || self.data.len() > MAX_LABEL_LENGTH { + return false; + } + + if let Some(first) = self.data.first() { + if !first.is_ascii_alphanumeric() && *first != b'_' { + return false; + } + } + + if !self + .data + .iter() + .skip(1) + .all(|c| c.is_ascii_alphanumeric() || *c == b'-' || *c == b'_') + { + return false; + } + + if let Some(last) = self.data.last() { + if !last.is_ascii_alphanumeric() { + return false; + } + } + + true + } +} + +impl Display for Label<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + let s = String::from_utf8_lossy(&self.data); + f.write_str(&s) + } +} + +impl Debug for Label<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("Label") + .field("data", &self.to_string()) + .finish() + } +} + +impl AsRef<[u8]> for Label<'_> { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::Cursor; + use crate::{lib::Vec, SimpleDnsError}; + + #[test] + fn construct_valid_names() { + assert!(Name::new("some").is_ok()); + assert!(Name::new("some.local").is_ok()); + assert!(Name::new("some.local.").is_ok()); + assert!(Name::new("some-dash.local.").is_ok()); + assert!(Name::new("_sync_miss._tcp.local").is_ok()); + assert!(Name::new("1sync_miss._tcp.local").is_ok()); + + assert_eq!(Name::new_unchecked("\u{1F600}.local.").labels.len(), 2); + } + + #[test] + fn label_validate() { + assert!(Name::new("\u{1F600}.local.").is_err()); + assert!(Name::new("@.local.").is_err()); + assert!(Name::new("\\.local.").is_err()); + } + + #[test] + fn is_link_local() { + assert!(!Name::new("some.example.com").unwrap().is_link_local()); + assert!(Name::new("some.example.local.").unwrap().is_link_local()); + } + + #[test] + fn parse_without_compression() { + let mut data = BytesBuffer::new( + b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\x01F\x03ISI\x04ARPA\x00\x04ARPA\x00", + ); + data.advance(3).unwrap(); + let name = Name::parse(&mut data).unwrap(); + assert_eq!("F.ISI.ARPA", name.to_string()); + + let name = Name::parse(&mut data).unwrap(); + assert_eq!("FOO.F.ISI.ARPA", name.to_string()); + } + + #[test] + fn parse_with_compression() { + let mut data = BytesBuffer::new(b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03\x07INVALID\xc0\x1b" ); + data.advance(3).unwrap(); + + let name = Name::parse(&mut data).unwrap(); + assert_eq!("F.ISI.ARPA", name.to_string()); + + let name = Name::parse(&mut data).unwrap(); + assert_eq!("FOO.F.ISI.ARPA", name.to_string()); + + let name = Name::parse(&mut data).unwrap(); + assert_eq!("BAR.F.ISI.ARPA", name.to_string()); + + assert!(Name::parse(&mut data).is_err()); + } + + #[test] + fn parse_handle_circular_pointers() { + let mut data = BytesBuffer::new(&[249, 0, 37, 1, 1, 139, 192, 6, 1, 1, 1, 139, 192, 6]); + data.advance(12).unwrap(); + + assert_eq!( + Name::parse(&mut data), + Err(SimpleDnsError::InvalidDnsPacket) + ); + } + + #[test] + fn test_write() { + let mut bytes = Vec::with_capacity(30); + Name::new_unchecked("_srv._udp.local") + .write_to(&mut bytes) + .unwrap(); + + assert_eq!(b"\x04_srv\x04_udp\x05local\x00", &bytes[..]); + + let mut bytes = Vec::with_capacity(30); + Name::new_unchecked("_srv._udp.local2.") + .write_to(&mut bytes) + .unwrap(); + + assert_eq!(b"\x04_srv\x04_udp\x06local2\x00", &bytes[..]); + } + + #[test] + fn root_name_should_generate_no_labels() { + assert_eq!(Name::new_unchecked("").labels.len(), 0); + assert_eq!(Name::new_unchecked(".").labels.len(), 0); + } + + #[test] + fn dot_sequence_should_generate_no_labels() { + assert_eq!(Name::new_unchecked(".....").labels.len(), 0); + assert_eq!(Name::new_unchecked("example.....com").labels.len(), 2); + } + + #[test] + fn root_name_should_write_zero() { + let mut bytes = Vec::with_capacity(30); + Name::new_unchecked(".").write_to(&mut bytes).unwrap(); + + assert_eq!(b"\x00", &bytes[..]); + } + + #[test] + fn append_to_vec_with_compression() { + let mut buf = Cursor::new(crate::lib::vec![0, 0, 0]); + buf.set_position(3); + + let mut name_refs = Default::default(); + + let f_isi_arpa = Name::new_unchecked("F.ISI.ARPA"); + f_isi_arpa + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add F.ISI.ARPA"); + let foo_f_isi_arpa = Name::new_unchecked("FOO.F.ISI.ARPA"); + foo_f_isi_arpa + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add FOO.F.ISI.ARPA"); + + Name::new_unchecked("BAR.F.ISI.ARPA") + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add FOO.F.ISI.ARPA"); + + let data = b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03"; + assert_eq!(data[..], buf.get_ref()[..]); + } + + #[test] + fn append_to_vec_with_compression_mult_names() { + let mut buf = Cursor::new(Vec::new()); + let mut name_refs = Default::default(); + + let isi_arpa = Name::new_unchecked("ISI.ARPA"); + isi_arpa + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add ISI.ARPA"); + + let f_isi_arpa = Name::new_unchecked("F.ISI.ARPA"); + f_isi_arpa + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add F.ISI.ARPA"); + let foo_f_isi_arpa = Name::new_unchecked("FOO.F.ISI.ARPA"); + foo_f_isi_arpa + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add F.ISI.ARPA"); + Name::new_unchecked("BAR.F.ISI.ARPA") + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add F.ISI.ARPA"); + + let expected = b"\x03ISI\x04ARPA\x00\x01F\xc0\x00\x03FOO\xc0\x0a\x03BAR\xc0\x0a"; + assert_eq!(expected[..], buf.get_ref()[..]); + + let mut data = BytesBuffer::new(buf.get_ref()); + + let first = Name::parse(&mut data).unwrap(); + assert_eq!("ISI.ARPA", first.to_string()); + let second = Name::parse(&mut data).unwrap(); + assert_eq!("F.ISI.ARPA", second.to_string()); + let third = Name::parse(&mut data).unwrap(); + assert_eq!("FOO.F.ISI.ARPA", third.to_string()); + let fourth = Name::parse(&mut data).unwrap(); + assert_eq!("BAR.F.ISI.ARPA", fourth.to_string()); + } + + #[test] + fn ensure_different_domains_are_not_compressed() { + let mut buf = Cursor::new(Vec::new()); + let mut name_refs = Default::default(); + + let foo_bar_baz = Name::new_unchecked("FOO.BAR.BAZ"); + foo_bar_baz + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add FOO.BAR.BAZ"); + + let foo_bar_buz = Name::new_unchecked("FOO.BAR.BUZ"); + foo_bar_buz + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add FOO.BAR.BUZ"); + + Name::new_unchecked("FOO.BAR") + .write_compressed_to(&mut buf, &mut name_refs) + .expect("failed to add FOO.BAR"); + + let expected = b"\x03FOO\x03BAR\x03BAZ\x00\x03FOO\x03BAR\x03BUZ\x00\x03FOO\x03BAR\x00"; + assert_eq!(expected[..], buf.get_ref()[..]); + } + + #[test] + fn eq_other_name() -> Result<(), SimpleDnsError> { + assert_eq!(Name::new("example.com")?, Name::new("example.com")?); + assert_ne!(Name::new("some.example.com")?, Name::new("example.com")?); + assert_ne!(Name::new("example.co")?, Name::new("example.com")?); + assert_ne!(Name::new("example.com.org")?, Name::new("example.com")?); + + let mut data = + BytesBuffer::new(b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03"); + data.advance(3)?; + assert_eq!(Name::new("F.ISI.ARPA")?, Name::parse(&mut data)?); + assert_eq!(Name::new("FOO.F.ISI.ARPA")?, Name::parse(&mut data)?); + Ok(()) + } + + #[test] + fn len() -> crate::Result<()> { + let mut bytes = Vec::new(); + let name_one = Name::new_unchecked("ex.com."); + name_one.write_to(&mut bytes)?; + + assert_eq!(8, bytes.len()); + assert_eq!(bytes.len(), name_one.len()); + assert_eq!(8, Name::parse(&mut BytesBuffer::new(&bytes))?.len()); + + Ok(()) + } + + #[test] + fn len_compressed() -> crate::Result<()> { + let name_one = Name::new_unchecked("ex.com."); + let mut name_refs = Default::default(); + let mut bytes = Cursor::new(Vec::new()); + name_one.write_compressed_to(&mut bytes, &mut name_refs)?; + name_one.write_compressed_to(&mut bytes, &mut name_refs)?; + + assert_eq!(10, bytes.get_ref().len()); + Ok(()) + } + + #[test] + #[cfg(feature = "std")] + fn hash() -> crate::Result<()> { + fn get_hash(name: &Name) -> u64 { + let mut hasher = std::hash::DefaultHasher::default(); + name.hash(&mut hasher); + hasher.finish() + } + + let mut data = + BytesBuffer::new(b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03"); + data.advance(3)?; + + assert_eq!( + get_hash(&Name::new("F.ISI.ARPA")?), + get_hash(&Name::parse(&mut data)?) + ); + + assert_eq!( + get_hash(&Name::new("FOO.F.ISI.ARPA")?), + get_hash(&Name::parse(&mut data)?) + ); + + Ok(()) + } + + #[test] + fn is_subdomain_of() { + assert!(Name::new_unchecked("sub.example.com") + .is_subdomain_of(&Name::new_unchecked("example.com"))); + + assert!(!Name::new_unchecked("example.com") + .is_subdomain_of(&Name::new_unchecked("example.com"))); + + assert!(Name::new_unchecked("foo.sub.example.com") + .is_subdomain_of(&Name::new_unchecked("example.com"))); + + assert!(!Name::new_unchecked("example.com") + .is_subdomain_of(&Name::new_unchecked("example.xom"))); + + assert!(!Name::new_unchecked("domain.com") + .is_subdomain_of(&Name::new_unchecked("other.domain"))); + + assert!(!Name::new_unchecked("domain.com") + .is_subdomain_of(&Name::new_unchecked("domain.com.br"))); + } + + #[test] + fn subtract_domain() { + let domain = Name::new_unchecked("_srv3._tcp.local"); + assert_eq!( + Name::new_unchecked("a._srv3._tcp.local") + .without(&domain) + .unwrap() + .to_string(), + "a" + ); + + assert!(Name::new_unchecked("unrelated").without(&domain).is_none(),); + + assert_eq!( + Name::new_unchecked("some.longer.domain._srv3._tcp.local") + .without(&domain) + .unwrap() + .to_string(), + "some.longer.domain" + ); + } + + #[test] + fn display_invalid_label() { + let input = b"invalid\xF0\x90\x80label"; + let label = Label::new_unchecked(input); + + assert_eq!(label.to_string(), "invalid�label"); + } + + #[test] + fn test_compress_append_near_boundary() -> crate::Result<()> { + let mut buf = Cursor::new(Vec::new()); + let mut name_refs = Default::default(); + + let before_boundary_pos = (MAX_COMPRESSION_OFFSET - 5) as usize; + let padding = vec![0u8; before_boundary_pos]; + buf.write_all(&padding)?; + + let name1 = Name::new_unchecked("foo.example.com"); + let name2 = Name::new_unchecked("bar.test.net"); + + // before the boundary + let old_pos = buf.position(); + name1.write_compressed_to(&mut buf, &mut name_refs)?; + assert_eq!(buf.position() - old_pos, name1.len() as u64); + + // after the boundary + let old_pos = buf.position(); + name2.write_compressed_to(&mut buf, &mut name_refs)?; + assert_eq!(buf.position() - old_pos, name2.len() as u64); + + // should be compressed + let old_pos = buf.position(); + name1.write_compressed_to(&mut buf, &mut name_refs)?; + assert_eq!(buf.position() - old_pos, 2); + + // should not be compressed + let old_pos = buf.position(); + name2.write_compressed_to(&mut buf, &mut name_refs)?; + assert_eq!(buf.position() - old_pos, name2.len() as u64); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/packet.rs b/vendor/simple-dns/src/dns/packet.rs new file mode 100644 index 00000000..e8d72739 --- /dev/null +++ b/vendor/simple-dns/src/dns/packet.rs @@ -0,0 +1,278 @@ +use super::{Header, PacketFlag, Question, ResourceRecord, WireFormat, OPCODE}; +use crate::{ + bytes_buffer::BytesBuffer, + lib::{Seek, Vec, Write}, + rdata::OPT, + RCODE, +}; + +/// Represents a DNS message packet +/// +/// When working with EDNS packets, use [Packet::opt] and [Packet::opt_mut] to add or access [OPT] packet information +#[derive(Debug, Clone)] +pub struct Packet<'a> { + /// Packet header + header: Header<'a>, + /// Questions section + pub questions: Vec>, + /// Answers section + pub answers: Vec>, + /// Name servers section + pub name_servers: Vec>, + /// Aditional records section. + /// DO NOT use this field to add OPT record, use [`Packet::opt_mut`] instead + pub additional_records: Vec>, +} + +impl<'a> Packet<'a> { + /// Creates a new empty packet with a query header + pub fn new_query(id: u16) -> Self { + Self { + header: Header::new_query(id), + questions: Vec::new(), + answers: Vec::new(), + name_servers: Vec::new(), + additional_records: Vec::new(), + } + } + + /// Creates a new empty packet with a reply header + pub fn new_reply(id: u16) -> Self { + Self { + header: Header::new_reply(id, OPCODE::StandardQuery), + questions: Vec::new(), + answers: Vec::new(), + name_servers: Vec::new(), + additional_records: Vec::new(), + } + } + + /// Get packet id + pub fn id(&self) -> u16 { + self.header.id + } + + /// Set packet id + pub fn set_id(&mut self, id: u16) { + self.header.id = id; + } + + /// Set flags in the packet + pub fn set_flags(&mut self, flags: PacketFlag) { + self.header.set_flags(flags); + } + + /// Remove flags present in the packet + pub fn remove_flags(&mut self, flags: PacketFlag) { + self.header.remove_flags(flags) + } + + /// Check if the packet has flags set + pub fn has_flags(&self, flags: PacketFlag) -> bool { + self.header.has_flags(flags) + } + + /// Get this packet [RCODE] information + pub fn rcode(&self) -> RCODE { + self.header.response_code + } + + /// Get a mutable reference for this packet [RCODE] information + /// Warning, if the [RCODE] value is greater than 15 (4 bits), you MUST provide an [OPT] + /// resource record through the [Packet::opt_mut] function + pub fn rcode_mut(&mut self) -> &mut RCODE { + &mut self.header.response_code + } + + /// Get this packet [OPCODE] information + pub fn opcode(&self) -> OPCODE { + self.header.opcode + } + + /// Get a mutable reference for this packet [OPCODE] information + pub fn opcode_mut(&mut self) -> &mut OPCODE { + &mut self.header.opcode + } + + /// Get the [OPT] resource record for this packet, if present + pub fn opt(&self) -> Option<&OPT<'a>> { + self.header.opt.as_ref() + } + + /// Get a mutable reference for this packet [OPT] resource record. + pub fn opt_mut(&mut self) -> &mut Option> { + &mut self.header.opt + } + + /// Changes this packet into a reply packet by replacing its header + pub fn into_reply(mut self) -> Self { + self.header = Header::new_reply(self.header.id, self.header.opcode); + self + } + + /// Parses a packet from a slice of bytes + pub fn parse(data: &'a [u8]) -> crate::Result { + let mut data = BytesBuffer::new(data); + let mut header = Header::parse(&mut data)?; + + let questions = Self::parse_section(&mut data, header.questions)?; + let answers = Self::parse_section(&mut data, header.answers)?; + let name_servers = Self::parse_section(&mut data, header.name_servers)?; + let mut additional_records: Vec = + Self::parse_section(&mut data, header.additional_records)?; + + header.extract_info_from_opt_rr( + additional_records + .iter() + .position(|rr| rr.rdata.type_code() == crate::TYPE::OPT) + .map(|i| additional_records.remove(i)), + ); + + Ok(Self { + header, + questions, + answers, + name_servers, + additional_records, + }) + } + + fn parse_section>( + data: &mut BytesBuffer<'a>, + items_count: u16, + ) -> crate::Result> { + let mut section_items = Vec::with_capacity(items_count as usize); + + for _ in 0..items_count { + section_items.push(T::parse(data)?); + } + + Ok(section_items) + } + + /// Creates a new [Vec``](`Vec`) and write the contents of this package in wire format + /// + /// This call will allocate a `Vec` of 900 bytes, which is enough for a jumbo UDP packet + pub fn build_bytes_vec(&self) -> crate::Result> { + let mut out = Vec::with_capacity(900); + self.write_to(&mut out)?; + + Ok(out) + } + + /// Creates a new [Vec``](`Vec`) and write the contents of this package in wire format + /// with compression enabled + /// + /// This call will allocate a `Vec` of 900 bytes, which is enough for a jumbo UDP packet + pub fn build_bytes_vec_compressed(&self) -> crate::Result> { + let mut out = crate::lib::Cursor::new(Vec::with_capacity(900)); + self.write_compressed_to(&mut out)?; + + Ok(out.into_inner()) + } + + /// Write the contents of this package in wire format into the provided writer + pub fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.write_header(out)?; + + for e in &self.questions { + e.write_to(out)?; + } + for e in &self.answers { + e.write_to(out)?; + } + for e in &self.name_servers { + e.write_to(out)?; + } + + if let Some(rr) = self.header.opt_rr() { + rr.write_to(out)?; + } + + for e in &self.additional_records { + e.write_to(out)?; + } + + out.flush()?; + Ok(()) + } + + /// Write the contents of this package in wire format with enabled compression into the provided writer + pub fn write_compressed_to(&self, out: &mut T) -> crate::Result<()> { + self.write_header(out)?; + + let mut name_refs = Default::default(); + for e in &self.questions { + e.write_compressed_to(out, &mut name_refs)?; + } + for e in &self.answers { + e.write_compressed_to(out, &mut name_refs)?; + } + for e in &self.name_servers { + e.write_compressed_to(out, &mut name_refs)?; + } + + if let Some(rr) = self.header.opt_rr() { + rr.write_to(out)?; + } + + for e in &self.additional_records { + e.write_compressed_to(out, &mut name_refs)?; + } + out.flush()?; + + Ok(()) + } + + fn write_header(&self, out: &mut T) -> crate::Result<()> { + self.header.write_to( + out, + self.questions.len() as u16, + self.answers.len() as u16, + self.name_servers.len() as u16, + self.additional_records.len() as u16 + u16::from(self.header.opt.is_some()), + ) + } +} + +#[cfg(test)] +mod tests { + use crate::{dns::CLASS, dns::TYPE, lib::ToString, SimpleDnsError}; + + use super::*; + + #[test] + fn parse_without_data_should_not_panic() { + assert!(matches!( + Packet::parse(&[]), + Err(SimpleDnsError::InsufficientData) + )); + } + + #[test] + fn build_query_correct() { + let mut query = Packet::new_query(1); + query.questions.push(Question::new( + "_srv._udp.local".try_into().unwrap(), + TYPE::TXT.into(), + CLASS::IN.into(), + false, + )); + query.questions.push(Question::new( + "_srv2._udp.local".try_into().unwrap(), + TYPE::TXT.into(), + CLASS::IN.into(), + false, + )); + + let query = query.build_bytes_vec().unwrap(); + + let parsed = Packet::parse(&query); + assert!(parsed.is_ok()); + + let parsed = parsed.unwrap(); + assert_eq!(2, parsed.questions.len()); + assert_eq!("_srv._udp.local", parsed.questions[0].qname.to_string()); + assert_eq!("_srv2._udp.local", parsed.questions[1].qname.to_string()); + } +} diff --git a/vendor/simple-dns/src/dns/question.rs b/vendor/simple-dns/src/dns/question.rs new file mode 100644 index 00000000..0b3cdabd --- /dev/null +++ b/vendor/simple-dns/src/dns/question.rs @@ -0,0 +1,142 @@ +use super::{Name, WireFormat, QCLASS, QTYPE}; +use crate::{ + bytes_buffer::BytesBuffer, + lib::{Seek, TryFrom, Write}, +}; + +/// Question represents a query in the DNS Packet +#[derive(Debug, Clone)] +pub struct Question<'a> { + /// a [Name](`Name`) to query for + pub qname: Name<'a>, + /// a [QTYPE](`QTYPE`) which specifies the type of the query. + pub qtype: QTYPE, + /// a [QCLASS](`QCLASS`) whire specifics the class of the query, For Example: IN + pub qclass: QCLASS, + /// indicates if the queries prefers a unicast response. + /// MDNS related, See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-5.4) + pub unicast_response: bool, +} + +impl<'a> Question<'a> { + /// Creates a new question + pub fn new(qname: Name<'a>, qtype: QTYPE, qclass: QCLASS, unicast_response: bool) -> Self { + Self { + qname, + qtype, + qclass, + unicast_response, + } + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> Question<'b> { + Question { + qname: self.qname.into_owned(), + qtype: self.qtype, + qclass: self.qclass, + unicast_response: self.unicast_response, + } + } + + fn write_common(&self, out: &mut T) -> crate::Result<()> { + let qclass: u16 = match self.unicast_response { + true => Into::::into(self.qclass) | 0x8000, + false => self.qclass.into(), + }; + + out.write_all(&Into::::into(self.qtype).to_be_bytes())?; + out.write_all(&qclass.to_be_bytes())?; + + Ok(()) + } +} + +impl<'a> WireFormat<'a> for Question<'a> { + const MINIMUM_LEN: usize = 4; + + // Disable redundant length check. + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result { + let qname = Name::parse(data)?; + + let qtype = data.get_u16()?; + let qclass = data.get_u16()?; + + Ok(Self { + qname, + qtype: QTYPE::try_from(qtype)?, + qclass: QCLASS::try_from(qclass & 0x7FFF)?, + unicast_response: qclass & 0x8000 == 0x8000, + }) + } + + fn len(&self) -> usize { + self.qname.len() + Self::MINIMUM_LEN + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.qname.write_to(out)?; + self.write_common(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.qname.write_compressed_to(out, name_refs)?; + self.write_common(out) + } +} + +#[cfg(test)] +mod tests { + use crate::{lib::Vec, CLASS, TYPE}; + + use super::*; + + #[test] + fn parse_question() { + let mut bytes = BytesBuffer::new(b"\x00\x00\x04_srv\x04_udp\x05local\x00\x00\x10\x00\x01"); + bytes.advance(2).unwrap(); + let question = Question::parse(&mut bytes); + + assert!(question.is_ok()); + let question = question.unwrap(); + + assert_eq!(QCLASS::CLASS(CLASS::IN), question.qclass); + assert_eq!(QTYPE::TYPE(TYPE::TXT), question.qtype); + assert!(!question.unicast_response); + } + + #[test] + fn append_to_vec() { + let question = Question::new( + "_srv._udp.local".try_into().unwrap(), + TYPE::TXT.into(), + CLASS::IN.into(), + false, + ); + let mut bytes = Vec::new(); + question.write_to(&mut bytes).unwrap(); + + assert_eq!(b"\x04_srv\x04_udp\x05local\x00\x00\x10\x00\x01", &bytes[..]); + assert_eq!(bytes.len(), question.len()); + } + + #[test] + fn unicast_response() { + let mut bytes = Vec::new(); + Question::new( + "x.local".try_into().unwrap(), + TYPE::TXT.into(), + CLASS::IN.into(), + true, + ) + .write_to(&mut bytes) + .unwrap(); + let parsed = Question::parse(&mut BytesBuffer::new(&bytes)).unwrap(); + + assert!(parsed.unicast_response); + } +} diff --git a/vendor/simple-dns/src/dns/rdata/a.rs b/vendor/simple-dns/src/dns/rdata/a.rs new file mode 100644 index 00000000..f5a09204 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/a.rs @@ -0,0 +1,88 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::WireFormat, + lib::{Ipv4Addr, Write}, +}; + +use super::RR; + +/// Represents a Resource Address (IPv4) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct A { + /// a 32 bit ip address + pub address: u32, +} + +impl RR for A { + const TYPE_CODE: u16 = 1; +} + +impl<'a> WireFormat<'a> for A { + const MINIMUM_LEN: usize = 4; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + data.get_u32().map(|address| Self { address }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.address.to_be_bytes()) + } +} + +impl A { + /// Transforms the inner data into its owned type + pub fn into_owned(self) -> Self { + self + } +} + +impl From for A { + fn from(addr: Ipv4Addr) -> Self { + Self { + address: addr.into(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + + use super::*; + + #[test] + fn parse_and_write_a() { + let a = A { + address: 2130706433, + }; + + let mut bytes = Vec::new(); + assert!(a.write_to(&mut bytes).is_ok()); + + let a = A::parse(&mut BytesBuffer::new(&bytes)); + assert!(a.is_ok()); + let a = a.unwrap(); + + assert_eq!(2130706433, a.address); + assert_eq!(bytes.len(), a.len()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_a = std::fs::read("samples/zonefile/A.sample.A")?; + let sample_ip: u32 = "26.3.0.103".parse::()?.into(); + + let sample_a_rdata = match ResourceRecord::parse(&mut BytesBuffer::new(&sample_a))?.rdata { + RData::A(a) => a, + _ => unreachable!(), + }; + + assert_eq!(sample_a_rdata.address, sample_ip); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/aaaa.rs b/vendor/simple-dns/src/dns/rdata/aaaa.rs new file mode 100644 index 00000000..a384694c --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/aaaa.rs @@ -0,0 +1,89 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::WireFormat, + lib::{Ipv6Addr, Write}, +}; + +use super::RR; + +/// Represents a Resource Address (IPv6) [rfc3596](https://tools.ietf.org/html/rfc3596) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct AAAA { + /// a 128 bit ip address + pub address: u128, +} + +impl RR for AAAA { + const TYPE_CODE: u16 = 28; +} + +impl WireFormat<'_> for AAAA { + const MINIMUM_LEN: usize = 16; + + fn parse(data: &mut BytesBuffer) -> crate::Result + where + Self: Sized, + { + data.get_u128().map(|address| Self { address }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.address.to_be_bytes()) + } +} + +impl AAAA { + /// Transforms the inner data into its owned type + pub fn into_owned(self) -> Self { + self + } +} + +impl From for AAAA { + fn from(ip: Ipv6Addr) -> Self { + Self { address: ip.into() } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::FromStr; + use crate::lib::Vec; + + use super::*; + + #[test] + fn parse_and_write_a() { + let address = Ipv6Addr::from_str("FF02::FB").unwrap(); + let aaaa = AAAA { + address: address.into(), + }; + + let mut bytes = Vec::new(); + assert!(aaaa.write_to(&mut bytes).is_ok()); + + let aaaa = AAAA::parse(&mut BytesBuffer::new(&bytes)); + assert!(aaaa.is_ok()); + let aaaa = aaaa.unwrap(); + + assert_eq!(address, Ipv6Addr::from(aaaa.address)); + assert_eq!(bytes.len(), aaaa.len()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + + let sample_file = std::fs::read("samples/zonefile/AAAA.sample")?; + let sample_ip: u128 = "fd92:7065:b8e:ffff::5".parse::()?.into(); + + let sample_rdata = match ResourceRecord::parse(&mut BytesBuffer::new(&sample_file))?.rdata { + RData::AAAA(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.address, sample_ip); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/afsdb.rs b/vendor/simple-dns/src/dns/rdata/afsdb.rs new file mode 100644 index 00000000..59d4e5e2 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/afsdb.rs @@ -0,0 +1,104 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// AFSDB records represents servers with ASD cells +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct AFSDB<'a> { + /// An integer that represents the subtype + pub subtype: u16, + /// A [name](`Name`) of a host that has a server for the cell named by the owner name of the RR + pub hostname: Name<'a>, +} + +impl RR for AFSDB<'_> { + const TYPE_CODE: u16 = 18; +} + +impl AFSDB<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> AFSDB<'b> { + AFSDB { + subtype: self.subtype, + hostname: self.hostname.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for AFSDB<'a> { + const MINIMUM_LEN: usize = 2; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let subtype = data.get_u16()?; + let hostname = Name::parse(data)?; + + Ok(Self { subtype, hostname }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.subtype.to_be_bytes())?; + self.hostname.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + out.write_all(&self.subtype.to_be_bytes())?; + self.hostname.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.hostname.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + use crate::lib::{ToString, Vec}; + + use super::*; + + #[test] + fn parse_and_write_afsdb() { + let afsdb = AFSDB { + subtype: 1, + hostname: Name::new("e.hostname.com").unwrap(), + }; + + let mut data = Vec::new(); + assert!(afsdb.write_to(&mut data).is_ok()); + + let afsdb = AFSDB::parse(&mut BytesBuffer::new(&data)); + assert!(afsdb.is_ok()); + let afsdb = afsdb.unwrap(); + + assert_eq!(data.len(), afsdb.len()); + assert_eq!(1, afsdb.subtype); + assert_eq!("e.hostname.com", afsdb.hostname.to_string()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/AFSDB.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut BytesBuffer::new(&sample_file))?.rdata { + RData::AFSDB(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.subtype, 0); + assert_eq!(sample_rdata.hostname, "hostname.sample".try_into()?); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/caa.rs b/vendor/simple-dns/src/dns/rdata/caa.rs new file mode 100644 index 00000000..46be553c --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/caa.rs @@ -0,0 +1,134 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{CharacterString, WireFormat}, + lib::{Cow, Write}, +}; + +use super::RR; + +/// RFC 8659: Allow domain name holders to indicate whether they are authorized to issue digital certificates for particular domain name +/// Used as a security policy for certificate authorities +/// This implementation does not validate the tag or value; it splits based on packet byte structure +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct CAA<'a> { + /// Critical or noncritical indicator + pub flag: u8, + /// Property described in the VALUE field. One of `issue`, `issuewild`, or `iodef` + pub tag: CharacterString<'a>, + /// Value associated with property tag + pub value: Cow<'a, [u8]>, +} + +impl RR for CAA<'_> { + const TYPE_CODE: u16 = 257; +} + +impl CAA<'_> { + /// Transforms the inner data into it owned type + pub fn into_owned<'b>(self) -> CAA<'b> { + CAA { + flag: self.flag, + tag: self.tag.into_owned(), + value: self.value.into_owned().into(), + } + } +} + +impl<'a> WireFormat<'a> for CAA<'a> { + const MINIMUM_LEN: usize = 1; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let flag = data.get_u8()?; + let tag = CharacterString::parse(data)?; + // FIXME: remove quotes if they are the first and last characters + let value = Cow::Borrowed(data.get_remaining()); + + Ok(Self { flag, tag, value }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.flag.to_be_bytes())?; + self.tag.write_to(out)?; + //FIXME: add quotes if the value is not already quoted + out.write_all(&self.value)?; + Ok(()) + } + + fn len(&self) -> usize { + self.tag.len() + self.value.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_caa() { + let caa = CAA { + flag: 0, + tag: CharacterString::new(b"issue").unwrap(), + value: b"\"example.org".into(), + }; + + let mut data = Vec::new(); + assert!(caa.write_to(&mut data).is_ok()); + + let caa = CAA::parse(&mut (&data[..]).into()); + assert!(caa.is_ok()); + let caa = caa.unwrap(); + + assert_eq!(data.len(), caa.len()); + assert_eq!(0, caa.flag); + assert_eq!("issue", caa.tag.to_string()); + assert_eq!(b"\"example.org", &caa.value[..]); + } + + #[test] + fn parse_rdata_with_multiple_caa_records() { + use crate::{rdata::RData, Packet, ResourceRecord, CLASS}; + + let mut packet = Packet::new_query(0); + packet.answers.push(ResourceRecord::new( + "caa.xxx.com".try_into().unwrap(), + CLASS::IN, + 11111, + crate::rdata::RData::CAA(CAA { + flag: 128, + tag: CharacterString::new(b"issuewild").unwrap(), + value: b"\"example.org".into(), + }), + )); + + packet.answers.push(ResourceRecord::new( + "caa.yyy.com".try_into().unwrap(), + CLASS::IN, + 11111, + crate::rdata::RData::CAA(CAA { + flag: 128, + tag: CharacterString::new(b"issuewild").unwrap(), + value: b"\"example_two.org".into(), + }), + )); + + let data = packet + .build_bytes_vec_compressed() + .expect("Failed to generate packet"); + + let mut packet = Packet::parse(&data[..]).expect("Failed to parse packet"); + let RData::CAA(cca_two) = packet.answers.pop().unwrap().rdata else { + panic!("failed to parse CAA record)") + }; + + let RData::CAA(cca_one) = packet.answers.pop().unwrap().rdata else { + panic!("failed to parse CAA record") + }; + + assert_eq!(b"\"example.org", &cca_one.value[..]); + assert_eq!(b"\"example_two.org", &cca_two.value[..]); + } +} diff --git a/vendor/simple-dns/src/dns/rdata/cert.rs b/vendor/simple-dns/src/dns/rdata/cert.rs new file mode 100644 index 00000000..59ab62d8 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/cert.rs @@ -0,0 +1,113 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Cow, lib::Write}; + +use super::RR; + +/// A Certificate record see [rfc4398](https://datatracker.ietf.org/doc/html/rfc4398) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct CERT<'a> { + /// The type of certificate (see RFC 4398 section 2.1) + pub type_code: u16, + /// The key tag value of the certificate public key + pub key_tag: u16, + /// The algorithm number describing the certificate's public key + pub algorithm: u8, + /// The certificate data in the format defined by the type_code + pub certificate: Cow<'a, [u8]>, +} + +impl RR for CERT<'_> { + const TYPE_CODE: u16 = 37; +} + +impl<'a> WireFormat<'a> for CERT<'a> { + const MINIMUM_LEN: usize = 5; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let type_code = data.get_u16()?; + let key_tag = data.get_u16()?; + let algorithm = data.get_u8()?; + let certificate = data.get_remaining(); + + Ok(Self { + type_code, + key_tag, + algorithm, + certificate: Cow::Borrowed(certificate), + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.type_code.to_be_bytes())?; + out.write_all(&self.key_tag.to_be_bytes())?; + out.write_all(&[self.algorithm])?; + out.write_all(&self.certificate)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.certificate.len() + Self::MINIMUM_LEN + } +} + +impl CERT<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> CERT<'b> { + CERT { + type_code: self.type_code, + key_tag: self.key_tag, + algorithm: self.algorithm, + certificate: self.certificate.into_owned().into(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::{vec, Vec}; + + use super::*; + + #[test] + fn parse_and_write_cert() { + let type_code = 12345u16; + let key_tag = 8u16; + let algorithm = 2u8; + let certificate = vec![1, 2, 3, 4, 5]; + let rdata = CERT { + type_code, + key_tag, + algorithm, + certificate: Cow::Owned(certificate), + }; + let mut writer = Vec::new(); + rdata.write_to(&mut writer).unwrap(); + let rdata = CERT::parse(&mut (&writer[..]).into()).unwrap(); + assert_eq!(rdata.type_code, type_code); + assert_eq!(rdata.key_tag, key_tag); + assert_eq!(rdata.algorithm, algorithm); + assert_eq!(&*rdata.certificate, &[1, 2, 3, 4, 5]); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/CERT.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::CERT(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.type_code, 3); + assert_eq!(sample_rdata.key_tag, 0); + assert_eq!(sample_rdata.algorithm, 0); + assert_eq!(*sample_rdata.certificate, *b"\x00\x00\x00\x00\x00"); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/dhcid.rs b/vendor/simple-dns/src/dns/rdata/dhcid.rs new file mode 100644 index 00000000..9e435932 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/dhcid.rs @@ -0,0 +1,102 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Cow, lib::Write}; + +use super::RR; + +/// A DHCID record see [rfc4701](https://datatracker.ietf.org/doc/html/rfc4701) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct DHCID<'a> { + /// Identifier type code + pub identifier: u16, + /// Digest type code + pub digest_type: u8, + /// Digest (length depends on digest type) + pub digest: Cow<'a, [u8]>, +} + +impl RR for DHCID<'_> { + const TYPE_CODE: u16 = 49; +} + +impl<'a> WireFormat<'a> for DHCID<'a> { + const MINIMUM_LEN: usize = 3; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let identifier = data.get_u16()?; + let digest_type = data.get_u8()?; + let digest = Cow::Borrowed(data.get_remaining()); + + Ok(Self { + identifier, + digest_type, + digest, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.identifier.to_be_bytes())?; + out.write_all(&[self.digest_type])?; + out.write_all(&self.digest)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.digest.len() + Self::MINIMUM_LEN + } +} + +impl DHCID<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> DHCID<'b> { + DHCID { + identifier: self.identifier, + digest_type: self.digest_type, + digest: Cow::Owned(self.digest.into_owned()), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + + use super::*; + + #[test] + fn parse_and_write_dhcid() { + let ds = DHCID { + identifier: 0, + digest_type: 0, + digest: Cow::Borrowed(&[0, 0, 0, 0]), + }; + + let mut data = Vec::new(); + ds.write_to(&mut data).unwrap(); + + let ds = DHCID::parse(&mut (&data[..]).into()).unwrap(); + assert_eq!(ds.identifier, 0); + assert_eq!(ds.digest_type, 0); + assert_eq!(ds.digest, Cow::Borrowed(&[0, 0, 0, 0])); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/DHCID.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::DHCID(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.identifier, 0x0002); + assert_eq!(sample_rdata.digest_type, 0x01); + assert_eq!(*sample_rdata.digest, *b"\x63\x6f\xc0\xb8\x27\x1c\x82\x82\x5b\xb1\xac\x5c\x41\xcf\x53\x51\xaa\x69\xb4\xfe\xbd\x94\xe8\xf1\x7c\xdb\x95\x00\x0d\xa4\x8c\x40"); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/dnskey.rs b/vendor/simple-dns/src/dns/rdata/dnskey.rs new file mode 100644 index 00000000..03000abd --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/dnskey.rs @@ -0,0 +1,115 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Cow, lib::Write}; + +use super::RR; + +/// A DNS key record see [rfc4034](https://www.rfc-editor.org/rfc/rfc4034#section-2) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct DNSKEY<'a> { + /// The flags field contains various flags that describe the key's properties + pub flags: u16, + /// The protocol field must be set to 3 per RFC4034 + pub protocol: u8, + /// The algorithm field identifies the public key's cryptographic algorithm + pub algorithm: u8, + /// The public key field contains the cryptographic key material in base64 format + pub public_key: Cow<'a, [u8]>, +} + +impl RR for DNSKEY<'_> { + const TYPE_CODE: u16 = 48; +} + +impl<'a> WireFormat<'a> for DNSKEY<'a> { + const MINIMUM_LEN: usize = 4; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let flags = data.get_u16()?; + let protocol = data.get_u8()?; + let algorithm = data.get_u8()?; + let public_key = Cow::Borrowed(data.get_remaining()); + + Ok(Self { + flags, + protocol, + algorithm, + public_key, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.flags.to_be_bytes())?; + out.write_all(&[self.protocol])?; + out.write_all(&[self.algorithm])?; + out.write_all(&self.public_key)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.public_key.len() + Self::MINIMUM_LEN + } +} + +impl DNSKEY<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> DNSKEY<'b> { + DNSKEY { + flags: self.flags, + protocol: self.protocol, + algorithm: self.algorithm, + public_key: Cow::Owned(self.public_key.into_owned()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{vec, Vec}; + + #[test] + fn parse_and_write_dnskey() { + let flags = 12345u16; + let protocol = 8u8; + let algorithm = 2u8; + let public_key = vec![1, 2, 3, 4, 5]; + let rdata = DNSKEY { + flags, + protocol, + algorithm, + public_key: Cow::Owned(public_key), + }; + let mut writer = Vec::new(); + rdata.write_to(&mut writer).unwrap(); + let rdata = DNSKEY::parse(&mut (&writer[..]).into()).unwrap(); + assert_eq!(rdata.flags, flags); + assert_eq!(rdata.protocol, protocol); + assert_eq!(rdata.algorithm, algorithm); + assert_eq!(&*rdata.public_key, &[1, 2, 3, 4, 5]); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/DNSKEY.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::DNSKEY(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.flags, 256); + assert_eq!(sample_rdata.protocol, 3); + assert_eq!(sample_rdata.algorithm, 5); + assert_eq!( + *sample_rdata.public_key, + *b"\x01\x03\xd2\x2a\x6c\xa7\x7f\x35\xb8\x93\x20\x6f\xd3\x5e\x4c\x50\x6d\x83\x78\x84\x37\x09\xb9\x7e\x04\x16\x47\xe1\xbf\xf4\x3d\x8d\x64\xc6\x49\xaf\x1e\x37\x19\x73\xc9\xe8\x91\xfc\xe3\xdf\x51\x9a\x8c\x84\x0a\x63\xee\x42\xa6\xd2\xeb\xdd\xbb\x97\x03\x5d\x21\x5a\xa4\xe4\x17\xb1\xfa\x45\xfa\x11\xa9\x74\x1e\xa2\x09\x8c\x1d\xfa\x5f\xb5\xfe\xb3\x32\xfd\x4b\xc8\x15\x20\x89\xae\xf3\x6b\xa6\x44\xcc\xe2\x41\x3b\x3b\x72\xbe\x18\xcb\xef\x8d\xa2\x53\xf4\xe9\x3d\x21\x03\x86\x6d\x92\x34\xa2\xe2\x8d\xf5\x29\xa6\x7d\x54\x68\xdb\xef\xe3" + ); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/ds.rs b/vendor/simple-dns/src/dns/rdata/ds.rs new file mode 100644 index 00000000..3caf1469 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/ds.rs @@ -0,0 +1,114 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Cow, lib::Write}; + +use super::RR; + +/// A DS record see [rfc4034](https://www.rfc-editor.org/rfc/rfc4034#section-5) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct DS<'a> { + /// The key tag is a 16-bit value used to identify the DNSKEY record referenced by this DS record + pub key_tag: u16, + /// The algorithm number identifying the cryptographic algorithm used to create the signature + pub algorithm: u8, + /// The digest type number identifying the cryptographic hash algorithm used to create the digest + pub digest_type: u8, + /// The digest value calculated over the referenced DNSKEY record + pub digest: Cow<'a, [u8]>, +} + +impl RR for DS<'_> { + const TYPE_CODE: u16 = 43; +} + +impl<'a> WireFormat<'a> for DS<'a> { + const MINIMUM_LEN: usize = 4; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let key_tag = data.get_u16()?; + let algorithm = data.get_u8()?; + let digest_type = data.get_u8()?; + let digest = Cow::Borrowed(data.get_remaining()); + + Ok(Self { + key_tag, + algorithm, + digest_type, + digest, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.key_tag.to_be_bytes())?; + out.write_all(&[self.algorithm, self.digest_type])?; + out.write_all(&self.digest)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.digest.len() + Self::MINIMUM_LEN + } +} + +impl DS<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> DS<'b> { + DS { + key_tag: self.key_tag, + algorithm: self.algorithm, + digest_type: self.digest_type, + digest: Cow::Owned(self.digest.into_owned()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{vec, Vec}; + + #[test] + fn parse_and_write_ds() { + let key_tag = 12345u16; + let algorithm = 8u8; + let digest_type = 2u8; + let digest = vec![1, 2, 3, 4, 5]; + let rdata = DS { + key_tag, + algorithm, + digest_type, + digest: Cow::Owned(digest), + }; + let mut writer = Vec::new(); + rdata.write_to(&mut writer).unwrap(); + let rdata = DS::parse(&mut (&writer[..]).into()).unwrap(); + assert_eq!(rdata.key_tag, key_tag); + assert_eq!(rdata.algorithm, algorithm); + assert_eq!(rdata.digest_type, digest_type); + assert_eq!(&*rdata.digest, &[1, 2, 3, 4, 5]); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/DS.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::DS(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.algorithm, 5); + assert_eq!(sample_rdata.digest_type, 1); + assert_eq!(sample_rdata.key_tag, 60485); + assert_eq!( + *sample_rdata.digest, + *b"\x2B\xB1\x83\xAF\x5F\x22\x58\x81\x79\xA5\x3B\x0A\x98\x63\x1F\xAD\x1A\x29\x21\x18" + ); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/eui.rs b/vendor/simple-dns/src/dns/rdata/eui.rs new file mode 100644 index 00000000..74b05279 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/eui.rs @@ -0,0 +1,145 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Write}; + +use super::RR; + +/// A 48 bit mac address +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct EUI48 { + /// A 48 bit mac address + pub address: [u8; 6], +} + +/// A 64 bit mac address +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct EUI64 { + /// A 64 bit mac address + pub address: [u8; 8], +} + +impl RR for EUI48 { + const TYPE_CODE: u16 = 108; +} + +impl RR for EUI64 { + const TYPE_CODE: u16 = 109; +} + +impl WireFormat<'_> for EUI48 { + const MINIMUM_LEN: usize = 6; + + fn parse(data: &mut BytesBuffer) -> crate::Result + where + Self: Sized, + { + let address = data.get_array()?; + Ok(Self { address }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.address) + } +} + +impl WireFormat<'_> for EUI64 { + const MINIMUM_LEN: usize = 8; + + fn parse(data: &mut BytesBuffer) -> crate::Result + where + Self: Sized, + { + let address = data.get_array()?; + Ok(Self { address }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.address) + } +} + +impl EUI48 { + /// Transforms the inner data into its owned type + pub fn into_owned(self) -> Self { + self + } +} + +impl EUI64 { + /// Transforms the inner data into its owned type + pub fn into_owned(self) -> Self { + self + } +} + +impl From for [u8; 6] { + fn from(value: EUI48) -> Self { + value.address + } +} + +impl From for [u8; 8] { + fn from(value: EUI64) -> Self { + value.address + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + #[cfg(feature = "std")] + use crate::{rdata::RData, ResourceRecord}; + + use super::*; + + #[test] + fn parse_and_write_eui48() { + let mac = [0, 0, 0, 0, 0, 0]; + let rdata = EUI48 { address: mac }; + let mut writer = Vec::new(); + rdata.write_to(&mut writer).unwrap(); + let rdata = EUI48::parse(&mut BytesBuffer::new(&writer)).unwrap(); + assert_eq!(rdata.address, mac); + } + + #[test] + fn parse_and_write_eui64() { + let mac = [0, 0, 0, 0, 0, 0, 0, 0]; + let rdata = EUI64 { address: mac }; + let mut writer = Vec::new(); + rdata.write_to(&mut writer).unwrap(); + let rdata = EUI64::parse(&mut (&writer[..]).into()).unwrap(); + assert_eq!(rdata.address, mac); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample_eui48() -> Result<(), Box> { + let sample_file = std::fs::read("samples/zonefile/EUI48.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::EUI48(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.address, [0x00, 0x00, 0x5e, 0x00, 0x53, 0x2a]); + + Ok(()) + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample_eui64() -> Result<(), Box> { + let sample_file = std::fs::read("samples/zonefile/EUI64.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::EUI64(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!( + sample_rdata.address, + [0x00, 0x00, 0x5e, 0xef, 0x10, 0x00, 0x00, 0x2a] + ); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/hinfo.rs b/vendor/simple-dns/src/dns/rdata/hinfo.rs new file mode 100644 index 00000000..7197c53b --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/hinfo.rs @@ -0,0 +1,105 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{CharacterString, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// HINFO records are used to acquire general information about a host. +/// The main use is for protocols such as FTP that can use special procedures +/// when talking between machines or operating systems of the same type. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct HINFO<'a> { + /// A [CharacterString](`CharacterString`) which specifies the CPU type. + pub cpu: CharacterString<'a>, + /// A [CharacterString](`CharacterString`) which specifies the operating system type. + pub os: CharacterString<'a>, +} + +impl RR for HINFO<'_> { + const TYPE_CODE: u16 = 13; +} + +impl HINFO<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> HINFO<'b> { + HINFO { + cpu: self.cpu.into_owned(), + os: self.os.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for HINFO<'a> { + const MINIMUM_LEN: usize = 0; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let cpu = CharacterString::parse(data)?; + let os = CharacterString::parse(data)?; + + Ok(Self { cpu, os }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.cpu.write_to(out)?; + self.os.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.cpu.write_compressed_to(out, name_refs)?; + self.os.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.cpu.len() + self.os.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_hinfo() { + let hinfo = HINFO { + cpu: CharacterString::new(b"\"some cpu").unwrap(), + os: CharacterString::new(b"\"some os").unwrap(), + }; + + let mut data = Vec::new(); + assert!(hinfo.write_to(&mut data).is_ok()); + + let hinfo = HINFO::parse(&mut (&data[..]).into()); + assert!(hinfo.is_ok()); + let hinfo = hinfo.unwrap(); + + assert_eq!(data.len(), hinfo.len()); + assert_eq!("\"some cpu", hinfo.cpu.to_string()); + assert_eq!("\"some os", hinfo.os.to_string()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/HINFO.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::HINFO(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.cpu, "Generic PC clone".try_into()?); + assert_eq!(sample_rdata.os, "NetBSD-1.4".try_into()?); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/ipseckey.rs b/vendor/simple-dns/src/dns/rdata/ipseckey.rs new file mode 100644 index 00000000..8f6e692c --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/ipseckey.rs @@ -0,0 +1,171 @@ +use super::RR; +use crate::{ + bytes_buffer::BytesBuffer, + dns::WireFormat, + lib::{Cow, Ipv4Addr, Ipv6Addr}, + lib::Write, + Name, +}; + +/// IPSECKEY record type stores information about IPsec key material +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct IPSECKEY<'a> { + /// Precedence for this record, lower values are preferred + pub precedence: u8, + /// Public key algorithm (1=DSA, 2=RSA) + pub algorithm: u8, + /// Domain name of the gateway + pub gateway: Gateway<'a>, + /// The public key material + pub public_key: Cow<'a, [u8]>, +} + +/// Gateway type for IPSECKEY records +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum Gateway<'a> { + /// No gateway + None, + /// IPv4 gateway + IPv4(Ipv4Addr), + /// IPv6 gateway + IPv6(Ipv6Addr), + /// Domain gateway + Domain(Name<'a>), +} + +impl Gateway<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> Gateway<'b> { + match self { + Gateway::None => Gateway::None, + Gateway::IPv4(x) => Gateway::IPv4(x), + Gateway::IPv6(x) => Gateway::IPv6(x), + Gateway::Domain(x) => Gateway::Domain(x.into_owned()), + } + } +} + +impl RR for IPSECKEY<'_> { + const TYPE_CODE: u16 = 45; +} + +impl<'a> WireFormat<'a> for IPSECKEY<'a> { + const MINIMUM_LEN: usize = 5; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let precedence = data.get_u8()?; + let gateway_type = data.get_u8()?; + let algorithm = data.get_u8()?; + let gateway = match gateway_type { + 0 => Gateway::None, + 1 => Gateway::IPv4(data.get_u32()?.into()), + 2 => Gateway::IPv6(data.get_u128()?.into()), + 3 => Gateway::Domain(Name::parse(data)?), + _ => return Err(crate::SimpleDnsError::AttemptedInvalidOperation), + }; + let public_key = data.get_remaining(); + Ok(Self { + precedence, + algorithm, + gateway, + public_key: Cow::Borrowed(public_key), + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + match &self.gateway { + Gateway::None => { + out.write_all(&[self.precedence, 0, self.algorithm])?; + } + Gateway::IPv4(ipv4_addr) => { + out.write_all(&[self.precedence, 1, self.algorithm])?; + out.write_all(&ipv4_addr.octets())? + } + Gateway::IPv6(ipv6_addr) => { + out.write_all(&[self.precedence, 2, self.algorithm])?; + out.write_all(&ipv6_addr.octets())? + } + Gateway::Domain(name) => { + out.write_all(&[self.precedence, 3, self.algorithm])?; + name.write_to(out)? + } + }; + out.write_all(&self.public_key)?; + Ok(()) + } + + fn len(&self) -> usize { + (match &self.gateway { + Gateway::None => 0, + Gateway::IPv4(_) => 4, + Gateway::IPv6(_) => 16, + Gateway::Domain(name) => name.len(), + }) + self.public_key.len() + + Self::MINIMUM_LEN + } +} + +impl IPSECKEY<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> IPSECKEY<'b> { + IPSECKEY { + precedence: self.precedence, + algorithm: self.algorithm, + gateway: self.gateway.into_owned(), + public_key: self.public_key.into_owned().into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::Vec; + + #[test] + fn parse_and_write_ipseckey() { + let ipseckey = IPSECKEY { + precedence: 10, + algorithm: 2, + gateway: Gateway::IPv4(Ipv4Addr::new(192,0,2,38)), + public_key: Cow::Borrowed(b"\x01\x03\x51\x53\x79\x86\xed\x35\x53\x3b\x60\x64\x47\x8e\xee\xb2\x7b\x5b\xd7\x4d\xae\x14\x9b\x6e\x81\xba\x3a\x05\x21\xaf\x82\xab\x78\x01"), + }; + + let mut data = Vec::new(); + ipseckey.write_to(&mut data).unwrap(); + + let ipseckey = IPSECKEY::parse(&mut (&data[..]).into()).unwrap(); + assert_eq!(ipseckey.precedence, 10); + assert_eq!(ipseckey.algorithm, 2); + assert_eq!( + ipseckey.gateway, + Gateway::IPv4(Ipv4Addr::new(192, 0, 2, 38)) + ); + assert_eq!(*ipseckey.public_key, *b"\x01\x03\x51\x53\x79\x86\xed\x35\x53\x3b\x60\x64\x47\x8e\xee\xb2\x7b\x5b\xd7\x4d\xae\x14\x9b\x6e\x81\xba\x3a\x05\x21\xaf\x82\xab\x78\x01"); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/IPSECKEY.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::IPSECKEY(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.precedence, 10); + assert_eq!(sample_rdata.algorithm, 2); + assert_eq!( + sample_rdata.gateway, + Gateway::IPv4(Ipv4Addr::new(192, 0, 2, 38)) + ); + assert_eq!(*sample_rdata.public_key, *b"\x01\x03\x51\x53\x79\x86\xed\x35\x53\x3b\x60\x64\x47\x8e\xee\xb2\x7b\x5b\xd7\x4d\xae\x14\x9b\x6e\x81\xba\x3a\x05\x21\xaf\x82\xab\x78\x01"); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/isdn.rs b/vendor/simple-dns/src/dns/rdata/isdn.rs new file mode 100644 index 00000000..dee97917 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/isdn.rs @@ -0,0 +1,102 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{CharacterString, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// An ISDN (Integrated Service Digital Network) number is simply a telephone number. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct ISDN<'a> { + /// A [CharacterString](`CharacterString`) which specifies the address. + pub address: CharacterString<'a>, + /// A [CharacterString](`CharacterString`) which specifies the subaddress. + pub sa: CharacterString<'a>, +} + +impl RR for ISDN<'_> { + const TYPE_CODE: u16 = 20; +} + +impl ISDN<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> ISDN<'b> { + ISDN { + address: self.address.into_owned(), + sa: self.sa.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for ISDN<'a> { + const MINIMUM_LEN: usize = 0; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let address = CharacterString::parse(data)?; + let sa = CharacterString::parse(data)?; + + Ok(Self { address, sa }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.address.write_to(out)?; + self.sa.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.address.write_compressed_to(out, name_refs)?; + self.sa.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.address.len() + self.sa.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_isdn() { + let isdn = ISDN { + address: CharacterString::new(b"150862028003217").unwrap(), + sa: CharacterString::new(b"004").unwrap(), + }; + + let mut data = Vec::new(); + assert!(isdn.write_to(&mut data).is_ok()); + + let isdn = ISDN::parse(&mut (&data[..]).into()); + assert!(isdn.is_ok()); + let isdn = isdn.unwrap(); + + assert_eq!(data.len(), isdn.len()); + assert_eq!("150862028003217", isdn.address.to_string()); + assert_eq!("004", isdn.sa.to_string()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/ISDN.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::ISDN(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.address, "isdn-address".try_into()?); + assert_eq!(sample_rdata.sa, "subaddress".try_into()?); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/kx.rs b/vendor/simple-dns/src/dns/rdata/kx.rs new file mode 100644 index 00000000..75576b12 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/kx.rs @@ -0,0 +1,91 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Write, Name}; + +use super::RR; + +/// A Key eXchange record [rfc2230](https://www.rfc-editor.org/rfc/rfc2230) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct KX<'a> { + /// The preference (or priority) lowest values are prioritized. + pub preference: u16, + /// The DNS domain name of the key exchanger. This host must have an associated KEY RR. + pub exchanger: Name<'a>, +} + +impl RR for KX<'_> { + const TYPE_CODE: u16 = 36; +} + +impl<'a> WireFormat<'a> for KX<'a> { + const MINIMUM_LEN: usize = 2; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let preference = data.get_u16()?; + let exchanger = Name::parse(data)?; + Ok(Self { + preference, + exchanger, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.preference.to_be_bytes())?; + self.exchanger.write_to(out)?; + Ok(()) + } + + fn len(&self) -> usize { + self.exchanger.len() + Self::MINIMUM_LEN + } +} + +impl KX<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> KX<'b> { + KX { + preference: self.preference, + exchanger: self.exchanger.into_owned(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + + use super::*; + + #[test] + fn parse_and_write_kx() { + let kx = KX { + preference: 5, + exchanger: Name::new("example.com.").unwrap(), + }; + + let mut data = Vec::new(); + kx.write_to(&mut data).unwrap(); + + let kx = KX::parse(&mut (&data[..]).into()).unwrap(); + assert_eq!(kx.preference, 5); + assert_eq!(kx.exchanger, Name::new("example.com.").unwrap()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/KX.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::KX(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.preference, 5); + assert_eq!(sample_rdata.exchanger, Name::new("example.com.")?); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/loc.rs b/vendor/simple-dns/src/dns/rdata/loc.rs new file mode 100644 index 00000000..45900980 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/loc.rs @@ -0,0 +1,138 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Write, SimpleDnsError}; + +use super::RR; + +/// A Means for Expressing Location Information in the Domain Name System [RFC 1876](https://datatracker.ietf.org/doc/html/rfc1876) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct LOC { + /// Version number of the representation. This must be zero. + pub version: u8, + /// The diameter of a sphere enclosing the described entity, in centimeters, expressed as a pair of four-bit unsigned integers + pub size: u8, + /// The horizontal precision of the data, in centimeters, expressed using the same representation as SIZE + pub horizontal_precision: u8, + /// The vertical precision of the data, in centimeters, expressed using the sane representation as for SIZE + pub vertical_precision: u8, + /// The latitude of the center of the sphere described by the SIZE field + pub latitude: i32, + /// The longitude of the center of the sphere described by the SIZE field + pub longitude: i32, + /// The altitude of the center of the sphere described by the SIZE field + pub altitude: i32, +} + +impl RR for LOC { + const TYPE_CODE: u16 = 29; +} + +impl LOC { + /// Transforms the inner data into its owned type + pub fn into_owned(self) -> Self { + self + } +} + +impl<'a> WireFormat<'a> for LOC { + const MINIMUM_LEN: usize = 16; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let version = data.get_u8()?; + if version != 0 { + return Err(SimpleDnsError::InvalidDnsPacket); + } + + let size = data.get_u8()?; + let horizontal_precision = data.get_u8()?; + let vertical_precision = data.get_u8()?; + let latitude = data.get_i32()?; + let longitude = data.get_i32()?; + let altitude = data.get_i32()?; + + Ok(LOC { + version, + size, + horizontal_precision, + vertical_precision, + latitude, + longitude, + altitude, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + if self.version != 0 { + return Err(SimpleDnsError::InvalidDnsPacket); + } + + out.write_all(&[ + self.version.to_be(), + self.size.to_be(), + self.horizontal_precision.to_be(), + self.vertical_precision.to_be(), + ])?; + out.write_all(&self.latitude.to_be_bytes())?; + out.write_all(&self.longitude.to_be_bytes())?; + out.write_all(&self.altitude.to_be_bytes())?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::Vec; + + #[test] + fn parse_and_write_loc() { + let loc = LOC { + version: 0, + size: 0x10, + vertical_precision: 0x11, + horizontal_precision: 0x12, + altitude: 1000, + longitude: 2000, + latitude: 3000, + }; + + let mut data = Vec::new(); + assert!(loc.write_to(&mut data).is_ok()); + + let loc = LOC::parse(&mut (&data[..]).into()); + assert!(loc.is_ok()); + let loc = loc.unwrap(); + + assert_eq!(0x10, loc.size); + assert_eq!(0x11, loc.vertical_precision); + assert_eq!(0x12, loc.horizontal_precision); + assert_eq!(1000, loc.altitude); + assert_eq!(2000, loc.longitude); + assert_eq!(3000, loc.latitude); + + assert_eq!(data.len(), loc.len()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/LOC.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut (&sample_file[..]).into())?.rdata { + RData::LOC(rdata) => rdata, + _ => unreachable!(), + }; + + // 60 09 00.000 N 24 39 00.000 E 10.00m 20.00m ( 2000.00m 20.00m ) + assert_eq!(35, sample_rdata.size); + assert_eq!(35, sample_rdata.vertical_precision); + assert_eq!(37, sample_rdata.horizontal_precision); + assert_eq!(10001000, sample_rdata.altitude); + assert_eq!(-2058743648, sample_rdata.longitude); + assert_eq!(-1930943648, sample_rdata.latitude); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/macros.rs b/vendor/simple-dns/src/dns/rdata/macros.rs new file mode 100644 index 00000000..5a8ebae0 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/macros.rs @@ -0,0 +1,231 @@ +macro_rules! rr_wrapper { + (#[doc=$doc:expr] $t:ident: $w:ident = $c:literal) => { + #[derive(Debug, PartialEq, Eq, Hash, Clone)] + #[doc = $doc] + pub struct $t<'a>(pub $w<'a>); + + impl<'a> RR for $t<'a> { + const TYPE_CODE: u16 = $c; + } + + impl<'a> From<$w<'a>> for $t<'a> { + fn from(value: $w<'a>) -> Self { + $t(value) + } + } + + impl<'a> $t<'a> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> $t<'b> { + $t(self.0.into_owned()) + } + } + + impl<'a> WireFormat<'a> for $t<'a> { + const MINIMUM_LEN: usize = 0; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + $w::parse(data).map(|n| $t(n)) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.0.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.0.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.0.len() + } + } + + impl<'a> crate::lib::Deref for $t<'a> { + type Target = $w<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl<'a> crate::lib::DerefMut for $t<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + }; +} + +macro_rules! rdata_enum { + ($($i:tt$(<$x:lifetime>)?,)+) => { + /// Represents the RData of each [`TYPE`] + #[derive(Debug, Eq, PartialEq, Hash, Clone)] + #[allow(missing_docs)] + pub enum RData<'a> { + $( + $i($i$(<$x>)?), + )+ + + NULL(u16, NULL<'a>), + Empty(TYPE) + } + + impl<'a> WireFormat<'a> for RData<'a> { + const MINIMUM_LEN: usize = 10; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let rdatatype = data.get_u16()?.into(); + let rdatalen = data.peek_u16_in(6)? as usize; + + // OPT needs to look the ttl and class values, hence position will be advanced by OPT + // parsing code + if rdatatype == TYPE::OPT { + let mut opt_data = data.new_limited_to(rdatalen + 8)?; + return Ok(RData::OPT(OPT::parse(&mut opt_data)?)) + } + + data.advance(8)?; + if rdatalen == 0 { + return Ok(RData::Empty(rdatatype)); + } + + let mut data = data.new_limited_to(rdatalen)?; + parse_rdata(&mut data, rdatatype) + } + + fn write_to( + &self, + out: &mut T, + ) -> crate::Result<()> { + match &self { + $( + RData::$i(data) => data.write_to(out), + )+ + + RData::NULL(_, data) => data.write_to(out), + RData::Empty(_) => { Ok(()) }, + } + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + match &self { + $( + RData::$i(data) => data.write_compressed_to(out, name_refs), + )+ + + RData::NULL(_, data) => data.write_compressed_to(out, name_refs), + RData::Empty(_) => { Ok(()) }, + } + } + + fn len(&self) -> usize { + match &self { + $( + RData::$i(data) => data.len(), + )+ + + RData::NULL(_, data) => data.len(), + RData::Empty(_) => 0, + } + } + } + + + + impl<'a> RData<'a> { + /// Returns the [`TYPE`] of this RData + pub fn type_code(&self) -> TYPE { + match self { + $( + RData::$i(_) => TYPE::$i, + )+ + + RData::NULL(type_code, _) => TYPE::Unknown(*type_code), + RData::Empty(ty) => *ty + } + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> RData<'b> { + match self { + $( + RData::$i(data) => RData::$i(data.into_owned()), + )+ + + RData::NULL(rdatatype, data) => RData::NULL(rdatatype, data.into_owned()), + RData::Empty(ty) => RData::Empty(ty) + } + } + } + + fn parse_rdata<'a>(data: &mut BytesBuffer<'a>, rdatatype: TYPE) -> crate::Result> { + let rdata = match rdatatype { + $( + TYPE::$i => RData::$i($i::parse(data)?), + )+ + + TYPE::NULL => RData::NULL(rdatatype.into(), NULL::parse(data)?), + TYPE::Unknown(rdatatype) => RData::NULL(rdatatype, NULL::parse(data)?), + }; + + Ok(rdata) + } + + + /// Possible TYPE values in DNS Resource Records + /// Each value is described according to its own RFC + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + #[allow(missing_docs)] + #[non_exhaustive] + pub enum TYPE { + $( $i,)+ + + NULL, + Unknown(u16) + } + + + impl From for u16 { + fn from(value: TYPE) -> Self { + match value { + $( + TYPE::$i => $i::TYPE_CODE, + )+ + + TYPE::NULL => NULL::TYPE_CODE, + TYPE::Unknown(x) => x, + } + } + } + + impl From for TYPE { + fn from(value: u16) -> Self { + match value { + $( + $i::TYPE_CODE => TYPE::$i, + )+ + + NULL::TYPE_CODE => TYPE::NULL, + v => TYPE::Unknown(v), + } + } + } + } +} + +pub(crate) use rdata_enum; +pub(crate) use rr_wrapper; diff --git a/vendor/simple-dns/src/dns/rdata/minfo.rs b/vendor/simple-dns/src/dns/rdata/minfo.rs new file mode 100644 index 00000000..1dd3f944 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/minfo.rs @@ -0,0 +1,87 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// MINFO recors are used to acquire mailbox or mail list information +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct MINFO<'a> { + /// A [Name](`Name`) which specifies a mailbox which is responsible for the mailing list or mailbox. + pub rmailbox: Name<'a>, + /// A [Name](`Name`) which specifies a mailbox which is to receive error messages related to + /// the mailing list or mailbox specified by the owner of the MINFO RR + pub emailbox: Name<'a>, +} + +impl RR for MINFO<'_> { + const TYPE_CODE: u16 = 14; +} + +impl MINFO<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> MINFO<'b> { + MINFO { + rmailbox: self.rmailbox.into_owned(), + emailbox: self.emailbox.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for MINFO<'a> { + const MINIMUM_LEN: usize = 0; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let rmailbox = Name::parse(data)?; + let emailbox = Name::parse(data)?; + + Ok(Self { rmailbox, emailbox }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.rmailbox.write_to(out)?; + self.emailbox.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.rmailbox.write_compressed_to(out, name_refs)?; + self.emailbox.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.rmailbox.len() + self.emailbox.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_minfo() { + let minfo = MINFO { + rmailbox: Name::new("r.mailbox.com").unwrap(), + emailbox: Name::new("e.mailbox.com").unwrap(), + }; + + let mut data = Vec::new(); + assert!(minfo.write_to(&mut data).is_ok()); + + let minfo = MINFO::parse(&mut (&data[..]).into()); + assert!(minfo.is_ok()); + let minfo = minfo.unwrap(); + + assert_eq!(data.len(), minfo.len()); + assert_eq!("r.mailbox.com", minfo.rmailbox.to_string()); + assert_eq!("e.mailbox.com", minfo.emailbox.to_string()); + } +} diff --git a/vendor/simple-dns/src/dns/rdata/mod.rs b/vendor/simple-dns/src/dns/rdata/mod.rs new file mode 100644 index 00000000..3ff6814d --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/mod.rs @@ -0,0 +1,243 @@ +#![allow(non_camel_case_types)] +//! Contains RData implementations + +use crate::{ + bytes_buffer::BytesBuffer, + lib::{Seek, Write}, + CharacterString, Name, WireFormat, +}; + +use core::fmt::Debug; + +mod macros; + +mod a; +pub use a::A; + +mod aaaa; +pub use aaaa::AAAA; + +mod afsdb; +pub use afsdb::AFSDB; + +mod caa; +pub use caa::CAA; + +mod hinfo; +pub use hinfo::HINFO; + +mod isdn; +pub use isdn::ISDN; + +mod loc; +pub use loc::LOC; + +mod minfo; +pub use minfo::MINFO; + +mod mx; +pub use mx::MX; + +mod naptr; +pub use naptr::NAPTR; + +mod nsap; +pub use nsap::NSAP; + +mod null; +pub use null::NULL; + +mod opt; +pub use opt::{OPTCode, OPT}; + +mod route_through; +pub use route_through::RouteThrough; + +mod rp; +pub use rp::RP; + +mod soa; +pub use soa::SOA; + +mod srv; +pub use srv::SRV; + +mod txt; +pub use txt::TXT; + +mod wks; +pub use wks::WKS; + +mod svcb; +pub use svcb::{SVCParam, SVCB}; + +mod eui; +pub use eui::EUI48; +pub use eui::EUI64; + +mod cert; +pub use cert::CERT; + +mod zonemd; +pub use zonemd::ZONEMD; + +mod kx; +pub use kx::KX; + +mod ipseckey; +pub use ipseckey::{Gateway, IPSECKEY}; + +mod dnskey; +pub use dnskey::DNSKEY; + +mod rrsig; +pub use rrsig::RRSIG; + +mod ds; +pub use ds::DS; + +mod nsec; +pub use nsec::{NsecTypeBitMap, NSEC}; + +mod dhcid; +pub use dhcid::DHCID; + +pub(crate) trait RR { + const TYPE_CODE: u16; +} + +macros::rr_wrapper! { + #[doc = "Authoritative name server, [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + NS:Name = 2 +} + +macros::rr_wrapper! { + #[doc = "Mail destination (Obsolete - use MX), [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + MD:Name = 3 +} + +macros::rr_wrapper! { + #[doc = "Mail forwarder (Obsolete - use MX), [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + MF:Name = 4 +} + +macros::rr_wrapper! { + #[doc = "Canonical name for an alias, [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + CNAME:Name = 5 +} + +macros::rr_wrapper! { + #[doc = "Mailbox domain name (EXPERIMENTAL), [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + MB:Name = 7 +} + +macros::rr_wrapper! { + #[doc = "Mail group member (EXPERIMENTAL), [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + MG: Name = 8 +} + +macros::rr_wrapper! { + #[doc = "Mail rename domain name (EXPERIMENTAL), [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + MR: Name = 9 +} + +macros::rr_wrapper! { + #[doc="Domain name pointer, [RFC 1035](https://tools.ietf.org/html/rfc1035)"] + PTR:Name = 12 +} + +macros::rr_wrapper! { + #[doc = "X.25 address, [RFC 1183](https://datatracker.ietf.org/doc/html/rfc1183#section-3.1)"] + X25:CharacterString = 19 +} + +macros::rr_wrapper! { + #[doc = "PTR for NSAP records, [RFC 1348](https://datatracker.ietf.org/doc/rfc1348/)"] + NSAP_PTR:Name = 23 +} + +macros::rr_wrapper! { + #[doc = "HTTPS RR type is a [SVCB]-compatible RR type, specific to the \"https\" and \"http\" schemes. \ + [RFC 9460](https://datatracker.ietf.org/doc/html/rfc9460#name-using-service-bindings-with)."] + HTTPS: SVCB = 65 +} + +macros::rdata_enum! { + A, + AAAA, + NS<'a>, + MD<'a>, + CNAME<'a>, + MB<'a>, + MG<'a>, + MR<'a>, + PTR<'a>, + MF<'a>, + HINFO<'a>, + MINFO<'a>, + MX<'a>, + TXT<'a>, + SOA<'a>, + WKS<'a>, + SRV<'a>, + RP<'a>, + AFSDB<'a>, + ISDN<'a>, + RouteThrough<'a>, + NAPTR<'a>, + NSAP, + NSAP_PTR<'a>, + LOC, + OPT<'a>, + CAA<'a>, + SVCB<'a>, + HTTPS<'a>, + EUI48, + EUI64, + CERT<'a>, + ZONEMD<'a>, + KX<'a>, + IPSECKEY<'a>, + DNSKEY<'a>, + RRSIG<'a>, + DS<'a>, + NSEC<'a>, + DHCID<'a>, +} + +/* +Not implemented + +SIG - RFC 2535 +KEY - RFC 2535 & RFC 2930 + +TKEY - RFC 2930 + +APL - RFC 3123 + +SSHFP - RFC 4255 + +DLV - RFC 4431 + +NSEC3PARAM - RFC 5155 +NSEC3 - RFC 5155 + +TLSA - RFC 6698 + +DNAME - RFC 6672 + +OPENPGPKEY - RFC 7929 + +URI - RFC 7553 + +CSYNC - RFC 7477 + +CDS - RFC 7344 +CDNSKEY - RFC 7344 + +SMIMEA - RFC 8162 + +HIP - RFC 8005 + +TSIG - RFC 8945 +*/ diff --git a/vendor/simple-dns/src/dns/rdata/mx.rs b/vendor/simple-dns/src/dns/rdata/mx.rs new file mode 100644 index 00000000..33a43847 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/mx.rs @@ -0,0 +1,108 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// MX is used to acquire mail exchange information +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct MX<'a> { + /// A 16 bit integer which specifies the preference given to this RR among others at the same owner. + /// Lower values are preferred. + pub preference: u16, + + /// A [Name](`Name`) which specifies a host willing to act as a mail exchange for the owner name. + pub exchange: Name<'a>, +} + +impl RR for MX<'_> { + const TYPE_CODE: u16 = 15; +} + +impl MX<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> MX<'b> { + MX { + preference: self.preference, + exchange: self.exchange.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for MX<'a> { + const MINIMUM_LEN: usize = 2; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let preference = data.get_u16()?; + let exchange = Name::parse(data)?; + + Ok(Self { + preference, + exchange, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.preference.to_be_bytes())?; + self.exchange.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + out.write_all(&self.preference.to_be_bytes())?; + self.exchange.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.exchange.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_mx() { + let mx = MX { + preference: 10, + exchange: Name::new("e.exchange.com").unwrap(), + }; + + let mut data = Vec::new(); + assert!(mx.write_to(&mut data).is_ok()); + + let mx = MX::parse(&mut data[..].into()); + assert!(mx.is_ok()); + let mx = mx.unwrap(); + + assert_eq!(data.len(), mx.len()); + assert_eq!(10, mx.preference); + assert_eq!("e.exchange.com", mx.exchange.to_string()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/MX.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::MX(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.preference, 10); + assert_eq!(sample_rdata.exchange, "VENERA.sample".try_into()?); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/naptr.rs b/vendor/simple-dns/src/dns/rdata/naptr.rs new file mode 100644 index 00000000..139e41eb --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/naptr.rs @@ -0,0 +1,120 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{CharacterString, Name, WireFormat}, + lib::Write, +}; + +use super::RR; + +/// RFC 3403: Used to map a domain name to a set of services. The fields determine +/// the order of processing, specify the protocol and service to be used, +/// and transform the original domain name into a new domain name or URI. + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct NAPTR<'a> { + /// Order in which NAPTR records must be processed + pub order: u16, + /// Order in which NAPTR records with equal Order values should be processed + pub preference: u16, + /// Control rewriting and interpretation of the fields in the record + pub flags: CharacterString<'a>, + /// Service Parameters applicable to this this delegation path + pub services: CharacterString<'a>, + /// Regular expression applied to original string from client + pub regexp: CharacterString<'a>, + /// Next domain-name to query for + pub replacement: Name<'a>, +} + +impl RR for NAPTR<'_> { + const TYPE_CODE: u16 = 35; +} + +impl NAPTR<'_> { + /// Transforms the inner data into it owned type + pub fn into_owned<'b>(self) -> NAPTR<'b> { + NAPTR { + order: self.order, + preference: self.preference, + flags: self.flags.into_owned(), + services: self.services.into_owned(), + regexp: self.regexp.into_owned(), + replacement: self.replacement.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for NAPTR<'a> { + const MINIMUM_LEN: usize = 4; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let order = data.get_u16()?; + let preference = data.get_u16()?; + let flags = CharacterString::parse(data)?; + let services = CharacterString::parse(data)?; + let regexp = CharacterString::parse(data)?; + let replacement = Name::parse(data)?; + + Ok(Self { + order, + preference, + flags, + services, + regexp, + replacement, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.order.to_be_bytes())?; + out.write_all(&self.preference.to_be_bytes())?; + self.flags.write_to(out)?; + self.services.write_to(out)?; + self.regexp.write_to(out)?; + self.replacement.write_to(out) + } + + fn len(&self) -> usize { + self.flags.len() + + self.services.len() + + self.regexp.len() + + self.replacement.len() + + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_naptr() { + let naptr = NAPTR { + order: 0, + preference: 1, + flags: CharacterString::new(b"123abc").unwrap(), + services: CharacterString::new(b"test").unwrap(), + regexp: CharacterString::new(b"@\\w+\\.\\w{2,3}(\\.\\w{2,3})?").unwrap(), + replacement: Name::new("e.exchange.com").unwrap(), + }; + + let mut data = Vec::new(); + assert!(naptr.write_to(&mut data).is_ok()); + + let naptr = NAPTR::parse(&mut data[..].into()); + assert!(naptr.is_ok()); + let naptr = naptr.unwrap(); + + assert_eq!(data.len(), naptr.len()); + assert_eq!(0, naptr.order); + assert_eq!(1, naptr.preference); + assert_eq!("123abc", naptr.flags.to_string()); + assert_eq!("test", naptr.services.to_string()); + assert_eq!("@\\w+\\.\\w{2,3}(\\.\\w{2,3})?", naptr.regexp.to_string()); + assert_eq!("e.exchange.com", naptr.replacement.to_string()); + } +} diff --git a/vendor/simple-dns/src/dns/rdata/nsap.rs b/vendor/simple-dns/src/dns/rdata/nsap.rs new file mode 100644 index 00000000..88d8e735 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/nsap.rs @@ -0,0 +1,154 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Write}; + +use super::RR; + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +/// NSAP structure [RFC 1706](https://datatracker.ietf.org/doc/html/rfc1706) +/// ATTENTION: this code doesn't validade the content of the NSAP RR, it just split the bytes in the correct order +pub struct NSAP { + /// Authority and Format Identifier + pub afi: u8, + /// Initial Domain Identifier + pub idi: u16, + /// DSP Format Identifier + pub dfi: u8, + /// Administrative Authority + pub aa: u32, + /// Reserved + pub rsvd: u16, + /// Routing Domain Identifier + pub rd: u16, + /// Area Identifier + pub area: u16, + /// System Identifier + pub id: u64, + /// NSAP Selector + pub sel: u8, +} + +impl RR for NSAP { + const TYPE_CODE: u16 = 22; +} + +impl NSAP { + /// Transforms the inner data into its owned type + pub fn into_owned(self) -> Self { + self + } +} + +impl<'a> WireFormat<'a> for NSAP { + const MINIMUM_LEN: usize = 20; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let afi = data.get_u8()?; + let idi = data.get_u16()?; + + let dfi = data.get_u8()?; + let aa: [u8; 3] = data.get_array()?; + let aa = u32::from_be_bytes([0, aa[0], aa[1], aa[2]]); + + let rsvd = data.get_u16()?; + let rd = data.get_u16()?; + + let area = data.get_u16()?; + let id: [u8; 6] = data.get_array()?; + let id = u64::from_be_bytes([0, 0, id[0], id[1], id[2], id[3], id[4], id[5]]); + let sel = data.get_u8()?; + + Ok(Self { + afi, + idi, + dfi, + aa, + rsvd, + rd, + area, + id, + sel, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&[self.afi.to_be()])?; + out.write_all(&self.idi.to_be_bytes())?; + out.write_all(&[self.dfi.to_be()])?; + out.write_all(&self.aa.to_be_bytes()[1..4])?; + out.write_all(&self.rsvd.to_be_bytes())?; + out.write_all(&self.rd.to_be_bytes())?; + out.write_all(&self.area.to_be_bytes())?; + out.write_all(&self.id.to_be_bytes()[2..8])?; + out.write_all(&[self.sel.to_be()])?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + + use super::*; + + #[test] + pub fn parse_and_write_nsap() { + let nsap = NSAP { + afi: 47, + idi: 5, + dfi: 0x80, + aa: 0x005a00, + rsvd: 0x10, + rd: 0x1000, + area: 0x0020, + id: 0x00800a123456, + sel: 0x10, + }; + + let mut data = Vec::new(); + assert!(nsap.write_to(&mut data).is_ok()); + assert_eq!(20, data.len()); + + let nsap = NSAP::parse(&mut data[..].into()); + assert!(nsap.is_ok()); + let nsap = nsap.unwrap(); + + assert_eq!(data.len(), nsap.len()); + assert_eq!(47, nsap.afi); + assert_eq!(5, nsap.idi); + assert_eq!(0x80, nsap.dfi); + assert_eq!(0x005a00, nsap.aa); + assert_eq!(0x10, nsap.rsvd); + assert_eq!(0x1000, nsap.rd); + assert_eq!(0x0020, nsap.area); + assert_eq!(0x00800a123456, nsap.id); + assert_eq!(0x10, nsap.sel); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/NSAP.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::NSAP(rdata) => rdata, + _ => unreachable!(), + }; + + // 0x47.0005.80.005a00.0000.0001.e133.ffffff000164.00 + assert_eq!(0x47, sample_rdata.afi); + assert_eq!(0x0005, sample_rdata.idi); + assert_eq!(0x80, sample_rdata.dfi); + assert_eq!(0x005a00, sample_rdata.aa); + assert_eq!(0x00, sample_rdata.rsvd); + assert_eq!(0x0001, sample_rdata.rd); + assert_eq!(0xe133, sample_rdata.area); + assert_eq!(0xffffff000164, sample_rdata.id); + assert_eq!(0x00, sample_rdata.sel); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/nsec.rs b/vendor/simple-dns/src/dns/rdata/nsec.rs new file mode 100644 index 00000000..775ba476 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/nsec.rs @@ -0,0 +1,187 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::WireFormat, + lib::Write, + lib::{Cow, Vec}, + Name, +}; + +use super::RR; + +/// A NSEC record see [rfc4034](https://datatracker.ietf.org/doc/html/rfc4034#section-4) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct NSEC<'a> { + /// The next owner name in the canonical ordering of the zone + pub next_name: Name<'a>, + /// The type bit maps representing the RR types present at the NSEC RR's owner name + pub type_bit_maps: Vec>, +} + +/// A Type bit map entry in a NSEC record see [rfc4034](https://datatracker.ietf.org/doc/html/rfc4034#section-4.1.2) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct NsecTypeBitMap<'a> { + /// The window block number of this bit map + pub window_block: u8, + /// The bitmap containing the RR types present in this window block + pub bitmap: Cow<'a, [u8]>, +} + +impl RR for NSEC<'_> { + const TYPE_CODE: u16 = 47; +} + +impl<'a> WireFormat<'a> for NSEC<'a> { + const MINIMUM_LEN: usize = 0; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let next_name = Name::parse(data)?; + let mut type_bit_maps = Vec::new(); + let mut prev_window_block = None; + + while data.has_remaining() { + let window_block = data.get_u8()?; + if let Some(prev_window_block) = prev_window_block { + if window_block <= prev_window_block { + return Err(crate::SimpleDnsError::InvalidDnsPacket); + } + } + + prev_window_block = Some(window_block); + + let bitmap_length = data.get_u8()? as usize; + if bitmap_length > 32 { + return Err(crate::SimpleDnsError::InvalidDnsPacket); + } + + let bitmap = data.get_slice(bitmap_length)?; + + type_bit_maps.push(NsecTypeBitMap { + window_block, + bitmap: Cow::Borrowed(bitmap), + }); + } + + Ok(Self { + next_name, + type_bit_maps, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.next_name.write_to(out)?; + + let mut sorted = self.type_bit_maps.clone(); + sorted.sort_by_key(|a| a.window_block); + + for record in sorted.iter() { + out.write_all(&[record.window_block])?; + out.write_all(&[record.bitmap.len() as u8])?; + out.write_all(&record.bitmap)?; + } + + Ok(()) + } + + fn len(&self) -> usize { + // To calculate the len it is necessary to get the len of all the bitmaps in the nsec record + // Type Bit Maps Field = ( Window Block # | Bitmap Length | Bitmap )+ + self.next_name.len() + + self + .type_bit_maps + .iter() + .map(|el| el.bitmap.len() + 2) // each type window has 2 bytes + bitmap data + .sum::() + } +} + +impl NSEC<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> NSEC<'b> { + let type_bit_maps = self + .type_bit_maps + .into_iter() + .map(|x| NsecTypeBitMap { + window_block: x.window_block, + bitmap: x.bitmap.into_owned().into(), + }) + .collect(); + NSEC { + next_name: self.next_name.into_owned(), + type_bit_maps, + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::lib::vec; + + #[test] + fn parse_and_write_nsec() { + let nsec = NSEC { + next_name: Name::new("host.example.com.").unwrap(), + type_bit_maps: vec![NsecTypeBitMap { + window_block: 0, + bitmap: vec![64, 1, 0, 0, 0, 1].into(), + }], + }; + let mut data = Vec::new(); + nsec.write_to(&mut data).unwrap(); + + let nsec = NSEC::parse(&mut data[..].into()).unwrap(); + assert_eq!(nsec.next_name, Name::new("host.example.com.").unwrap()); + assert_eq!(nsec.type_bit_maps.len(), 1); + assert_eq!(nsec.type_bit_maps[0].window_block, 0); + assert_eq!(nsec.type_bit_maps[0].bitmap, vec![64, 1, 0, 0, 0, 1]); + } + + #[test] + fn nsec_reports_correct_len() { + let nsec = NSEC { + next_name: Name::new("host.example.com.").unwrap(), + type_bit_maps: vec![ + NsecTypeBitMap { + window_block: 0, + bitmap: vec![0x40, 0x01, 0x00, 0x00, 0x00, 0x01].into(), + }, + NsecTypeBitMap { + window_block: 1, + bitmap: vec![0x40, 0x01, 0x00, 0x00, 0x00, 0x01].into(), + }, + ], + }; + + let mut buffer = Vec::new(); + nsec.write_to(&mut buffer).unwrap(); + assert_eq!(buffer.len(), nsec.len()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/NSEC.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::NSEC(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!( + sample_rdata.next_name, + Name::new("host.example.com.").unwrap() + ); + assert_eq!(sample_rdata.type_bit_maps.len(), 1); + assert_eq!(sample_rdata.type_bit_maps[0].window_block, 0); + assert_eq!( + sample_rdata.type_bit_maps[0].bitmap, + vec![64, 1, 0, 0, 0, 1] + ); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/null.rs b/vendor/simple-dns/src/dns/rdata/null.rs new file mode 100644 index 00000000..3773a1a0 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/null.rs @@ -0,0 +1,64 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{WireFormat, MAX_NULL_LENGTH}, + lib::Cow, + lib::Write, +}; + +use super::RR; + +/// NULL resources are used to represent any kind of information. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct NULL<'a> { + length: u16, + data: Cow<'a, [u8]>, +} + +impl RR for NULL<'_> { + const TYPE_CODE: u16 = 10; +} + +impl<'a> NULL<'a> { + /// Creates a new NULL rdata + pub fn new(data: &'a [u8]) -> crate::Result { + if data.len() > MAX_NULL_LENGTH { + return Err(crate::SimpleDnsError::InvalidDnsPacket); + } + + Ok(Self { + length: data.len() as u16, + data: Cow::Borrowed(data), + }) + } + + /// get a read only reference to internal data + pub fn get_data(&'_ self) -> &'_ [u8] { + &self.data + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> NULL<'b> { + NULL { + length: self.length, + data: self.data.into_owned().into(), + } + } +} + +impl<'a> WireFormat<'a> for NULL<'a> { + const MINIMUM_LEN: usize = 0; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + Self::new(data.get_remaining()) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.data) + } + + fn len(&self) -> usize { + self.length as usize + } +} diff --git a/vendor/simple-dns/src/dns/rdata/opt.rs b/vendor/simple-dns/src/dns/rdata/opt.rs new file mode 100644 index 00000000..4b43aa88 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/opt.rs @@ -0,0 +1,224 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{header::Header, WireFormat}, + lib::Cow, + lib::Vec, + lib::Write, + RCODE, +}; + +use super::RR; + +pub mod masks { + pub const RCODE_MASK: u32 = 0xFF00_0000; + pub const VERSION_MASK: u32 = 0x00FF_0000; +} + +/// OPT is a pseudo-rr used to carry control information +/// If an OPT record is present in a received request, responders MUST include an OPT record in their respective responses. +/// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files. +/// +/// There must be only one OPT record in the message. +/// If a query message with more than one OPT RR is received, a FORMERR (RCODE=1) MUST be returned. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct OPT<'a> { + /// The variable part of this OPT RR + pub opt_codes: Vec>, + /// UDP packet size supported by the responder + pub udp_packet_size: u16, + + /// EDNS version supported by the responder + pub version: u8, +} + +impl RR for OPT<'_> { + const TYPE_CODE: u16 = 41; +} + +impl<'a> WireFormat<'a> for OPT<'a> { + const MINIMUM_LEN: usize = 10; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + // first 2 bytes where already skiped in the RData parse + + // udp packet size comes from CLASS + let udp_packet_size = data.get_u16()?; + // version comes from ttl + let ttl = data.get_u32()?; + let version = ((ttl & masks::VERSION_MASK) >> masks::VERSION_MASK.trailing_zeros()) as u8; + + data.advance(2)?; + + let mut opt_codes = Vec::new(); + while data.has_remaining() { + let code = data.get_u16()?; + let length = data.get_u16()? as usize; // length is the length of the data field in bytes + + let inner_data = Cow::Borrowed(data.get_slice(length)?); + opt_codes.push(OPTCode { + code, + data: inner_data, + }); + } + + Ok(Self { + opt_codes, + udp_packet_size, + version, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + for code in self.opt_codes.iter() { + out.write_all(&code.code.to_be_bytes())?; + out.write_all(&(code.data.len() as u16).to_be_bytes())?; + out.write_all(&code.data)?; + } + + Ok(()) + } + + fn len(&self) -> usize { + self.opt_codes.iter().map(|o| o.data.len() + 4).sum() + } +} + +impl OPT<'_> { + pub(crate) fn extract_rcode_from_ttl(ttl: u32, header: &Header) -> RCODE { + let mut rcode = ((ttl & masks::RCODE_MASK) >> 24) << 4; + rcode |= header.response_code as u32; + RCODE::from(rcode as u16) + } + + pub(crate) fn encode_ttl(&self, header: &Header) -> u32 { + let mut ttl: u32 = ((header.response_code as u32 >> 4) & 0xFF) << 24; + ttl |= (self.version as u32) << masks::VERSION_MASK.trailing_zeros(); + ttl + } + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> OPT<'b> { + OPT { + // length: self.length, + udp_packet_size: self.udp_packet_size, + version: self.version, + opt_codes: self.opt_codes.into_iter().map(|o| o.into_owned()).collect(), + } + } +} + +/// Represents the variable part of an OPT rr +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct OPTCode<'a> { + // TODO: include an OPT_CODE enum??? + /// Assigned by the Expert Review process as defined by the DNSEXT working group and the IESG. + pub code: u16, + /// Varies per OPTION-CODE. MUST be treated as a bit field. + pub data: Cow<'a, [u8]>, +} + +impl OPTCode<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> OPTCode<'b> { + OPTCode { + code: self.code, + data: self.data.into_owned().into(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::vec; + use crate::{rdata::RData, Name, ResourceRecord}; + + use super::*; + + #[test] + fn parse_and_write_opt_empty() { + let header = Header::new_reply(1, crate::OPCODE::StandardQuery); + + let opt = OPT { + udp_packet_size: 500, + version: 2, + opt_codes: Vec::new(), + }; + let opt_rr = ResourceRecord { + ttl: opt.encode_ttl(&header), + name: Name::new_unchecked("."), + class: crate::CLASS::IN, + cache_flush: false, + rdata: RData::OPT(opt), + }; + + let mut data = Vec::new(); + assert!(opt_rr.write_to(&mut data).is_ok()); + + let opt = match ResourceRecord::parse(&mut data[..].into()) + .expect("failed to parse") + .rdata + { + RData::OPT(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(data.len(), opt_rr.len()); + assert_eq!(500, opt.udp_packet_size); + assert_eq!(2, opt.version); + assert!(opt.opt_codes.is_empty()); + } + + #[test] + fn parse_and_write_opt() { + let header = Header::new_reply(1, crate::OPCODE::StandardQuery); + + let opt = OPT { + udp_packet_size: 500, + version: 2, + opt_codes: vec![ + OPTCode { + code: 1, + data: Cow::Owned(vec![255, 255]), + }, + OPTCode { + code: 2, + data: Cow::Owned(vec![255, 255, 255]), + }, + ], + }; + + let opt_rr = ResourceRecord { + ttl: opt.encode_ttl(&header), + name: Name::new_unchecked("."), + class: crate::CLASS::IN, + cache_flush: false, + rdata: RData::OPT(opt), + }; + + let mut data = Vec::new(); + assert!(opt_rr.write_to(&mut data).is_ok()); + + let mut opt = match ResourceRecord::parse(&mut data[..].into()) + .expect("failed to parse") + .rdata + { + RData::OPT(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(data.len(), opt_rr.len()); + assert_eq!(500, opt.udp_packet_size); + assert_eq!(2, opt.version); + assert_eq!(2, opt.opt_codes.len()); + + let opt_code = opt.opt_codes.pop().unwrap(); + assert_eq!(2, opt_code.code); + assert_eq!(vec![255, 255, 255], *opt_code.data); + + let opt_code = opt.opt_codes.pop().unwrap(); + assert_eq!(1, opt_code.code); + assert_eq!(vec![255, 255], *opt_code.data); + } +} diff --git a/vendor/simple-dns/src/dns/rdata/route_through.rs b/vendor/simple-dns/src/dns/rdata/route_through.rs new file mode 100644 index 00000000..448dcad6 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/route_through.rs @@ -0,0 +1,111 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// The RT resource record provides a route-through binding for hosts that do not have their own direct wide area network addresses +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct RouteThrough<'a> { + /// A 16 bit integer which specifies the preference given to this RR among others at the same owner. + /// Lower values are preferred. + pub preference: u16, + + /// A [Name](`Name`) which specifies a host which will serve as an intermediate in reaching the host specified by **owner**. + pub intermediate_host: Name<'a>, +} + +impl RR for RouteThrough<'_> { + const TYPE_CODE: u16 = 21; +} + +impl RouteThrough<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> RouteThrough<'b> { + RouteThrough { + preference: self.preference, + intermediate_host: self.intermediate_host.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for RouteThrough<'a> { + const MINIMUM_LEN: usize = 2; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let preference = data.get_u16()?; + let intermediate_host = Name::parse(data)?; + + Ok(Self { + preference, + intermediate_host, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.preference.to_be_bytes())?; + self.intermediate_host.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + out.write_all(&self.preference.to_be_bytes())?; + self.intermediate_host.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.intermediate_host.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_route_through() { + let rt = RouteThrough { + preference: 10, + intermediate_host: Name::new("e.exchange.com").unwrap(), + }; + + let mut data = Vec::new(); + assert!(rt.write_to(&mut data).is_ok()); + + let rt = RouteThrough::parse(&mut data[..].into()); + assert!(rt.is_ok()); + let rt = rt.unwrap(); + + assert_eq!(data.len(), rt.len()); + assert_eq!(10, rt.preference); + assert_eq!("e.exchange.com", rt.intermediate_host.to_string()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/RT.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::RouteThrough(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.preference, 0); + assert_eq!( + sample_rdata.intermediate_host, + "intermediate-host.sample".try_into()? + ); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/rp.rs b/vendor/simple-dns/src/dns/rdata/rp.rs new file mode 100644 index 00000000..68478183 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/rp.rs @@ -0,0 +1,103 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// RP Responsible Person, [RFC 1183](https://datatracker.ietf.org/doc/html/rfc1183#section-2.2) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct RP<'a> { + /// A [Name](`Name`) which specifies a mailbox for the responsble person. + pub mbox: Name<'a>, + /// A [Name](`Name`) which specifies a domain name the TXT records. + pub txt: Name<'a>, +} + +impl RR for RP<'_> { + const TYPE_CODE: u16 = 17; +} + +impl RP<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> RP<'b> { + RP { + mbox: self.mbox.into_owned(), + txt: self.txt.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for RP<'a> { + const MINIMUM_LEN: usize = 0; + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let mbox = Name::parse(data)?; + let txt = Name::parse(data)?; + + Ok(RP { mbox, txt }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.mbox.write_to(out)?; + self.txt.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.mbox.write_compressed_to(out, name_refs)?; + self.txt.write_compressed_to(out, name_refs) + } + + fn len(&self) -> usize { + self.txt.len() + self.mbox.len() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::lib::{ToString, Vec}; + + #[test] + fn parse_and_write_rp() { + let rp = RP { + mbox: Name::new("mbox.rp.com").unwrap(), + txt: Name::new("txt.rp.com").unwrap(), + }; + + let mut data = Vec::new(); + assert!(rp.write_to(&mut data).is_ok()); + + let rp = RP::parse(&mut data[..].into()); + assert!(rp.is_ok()); + let rp = rp.unwrap(); + + assert_eq!(data.len(), rp.len()); + assert_eq!("mbox.rp.com", rp.mbox.to_string()); + assert_eq!("txt.rp.com", rp.txt.to_string()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/RP.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::RP(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.mbox, "mbox-dname.sample".try_into()?); + assert_eq!(sample_rdata.txt, "txt-dname.sample".try_into()?); + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/rrsig.rs b/vendor/simple-dns/src/dns/rdata/rrsig.rs new file mode 100644 index 00000000..cb82c433 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/rrsig.rs @@ -0,0 +1,152 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::Cow, + lib::Write, +}; + +use super::RR; + +/// An RRSIG record see [rfc4034](https://www.rfc-editor.org/rfc/rfc4034#section-3) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct RRSIG<'a> { + /// The type of RR that is covered by this RRSIG + pub type_covered: u16, + /// The cryptographic algorithm used for the signature + pub algorithm: u8, + /// The number of labels in the original RRSIG RR owner name + pub labels: u8, + /// The original TTL value of the covered record + pub original_ttl: u32, + /// When the signature expires (seconds since Jan 1 1970) + pub signature_expiration: u32, + /// When the signature was created (seconds since Jan 1 1970) + pub signature_inception: u32, + /// Key tag value of the DNSKEY RR that validates this signature + pub key_tag: u16, + /// The domain name of the zone that contains the signed RRset + pub signer_name: Name<'a>, + /// The cryptographic signature that covers the RRSIG RDATA + pub signature: Cow<'a, [u8]>, +} + +impl RR for RRSIG<'_> { + const TYPE_CODE: u16 = 46; +} + +impl<'a> WireFormat<'a> for RRSIG<'a> { + const MINIMUM_LEN: usize = 18; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let type_covered = data.get_u16()?; + let algorithm = data.get_u8()?; + let labels = data.get_u8()?; + let original_ttl = data.get_u32()?; + let signature_expiration = data.get_u32()?; + let signature_inception = data.get_u32()?; + let key_tag = data.get_u16()?; + + let signer_name = Name::parse(data)?; + let signature = Cow::Borrowed(data.get_remaining()); + + Ok(Self { + type_covered, + algorithm, + labels, + original_ttl, + signature_expiration, + signature_inception, + key_tag, + signer_name, + signature, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.type_covered.to_be_bytes())?; + out.write_all(&[self.algorithm])?; + out.write_all(&[self.labels])?; + out.write_all(&self.original_ttl.to_be_bytes())?; + out.write_all(&self.signature_expiration.to_be_bytes())?; + out.write_all(&self.signature_inception.to_be_bytes())?; + out.write_all(&self.key_tag.to_be_bytes())?; + self.signer_name.write_to(out)?; + out.write_all(&self.signature)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.signer_name.len() + self.signature.len() + Self::MINIMUM_LEN + } +} + +impl RRSIG<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> RRSIG<'b> { + RRSIG { + type_covered: self.type_covered, + algorithm: self.algorithm, + labels: self.labels, + original_ttl: self.original_ttl, + signature_expiration: self.signature_expiration, + signature_inception: self.signature_inception, + key_tag: self.key_tag, + signer_name: self.signer_name.into_owned(), + signature: Cow::Owned(self.signature.into_owned()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{lib::Vec, rdata::A}; + + #[test] + fn parse_and_write_rrsig() { + let rrsig = RRSIG { + type_covered: A::TYPE_CODE, + algorithm: 5, + labels: 3, + original_ttl: 86400, + signature_expiration: 1045762263, + signature_inception: 1048354263, + key_tag: 2642, + signer_name: Name::new("example.com.").unwrap(), + signature: b"TEST".to_vec().into(), + }; + + let mut data = Vec::new(); + rrsig.write_to(&mut data).unwrap(); + let rrsig2 = RRSIG::parse(&mut data[..].into()).unwrap(); + assert_eq!(rrsig, rrsig2); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/RRSIG.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::RRSIG(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.type_covered, A::TYPE_CODE); + assert_eq!(sample_rdata.algorithm, 5); + assert_eq!(sample_rdata.labels, 3); + assert_eq!(sample_rdata.original_ttl, 86400); + assert_eq!(sample_rdata.signature_expiration, 1048354263); + assert_eq!(sample_rdata.signature_inception, 1045762263); + assert_eq!(sample_rdata.key_tag, 2642); + assert_eq!(sample_rdata.signer_name, Name::new("example.com.")?); + assert_eq!(*sample_rdata.signature, *b"\xa0\x90\x75\x5b\xa5\x8d\x1a\xff\xa5\x76\xf4\x37\x58\x31\xb4\x31\x09\x20\xe4\x81\x21\x8d\x18\xa9\xf1\x64\xeb\x3d\x81\xaf\xd3\xb8\x75\xd3\xc7\x54\x28\x63\x1e\x0c\xf2\xa2\x8d\x50\x87\x5f\x70\xc3\x29\xd7\xdb\xfa\xfe\xa8\x07\xdc\x1f\xba\x1d\xc3\x4c\x95\xd4\x01\xf2\x3f\x33\x4c\xe6\x3b\xfc\xf3\xf1\xb5\xb4\x47\x39\xe5\xf0\xed\xed\x18\xd6\xb3\x3f\x04\x0a\x91\x13\x76\xd1\x73\xd7\x57\xa9\xf0\xc1\xfa\x17\x98\x94\x1b\xb0\xb3\x6b\x2d\xf9\x06\x27\x90\xfa\x7f\x01\x66\xf2\x73\x7e\xea\x90\x73\x78\x34\x1f\xb1\x2d\xc0\xa7\x7a"); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/soa.rs b/vendor/simple-dns/src/dns/rdata/soa.rs new file mode 100644 index 00000000..111cc373 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/soa.rs @@ -0,0 +1,161 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::{Name, WireFormat}, + lib::{Seek, Write}, +}; + +use super::RR; + +/// SOA records are used to mark the start of a zone of authority +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct SOA<'a> { + /// The [Name](`Name`) of the name server that was the original or primary source of data for this zone. + pub mname: Name<'a>, + /// A [Name](`Name`) which specifies the mailbox of the person responsible for this zone. + pub rname: Name<'a>, + /// The unsigned 32 bit version number of the original copy of the zone. Zone transfers preserve this value. + /// This value wraps and should be compared using sequence space arithmetic. + pub serial: u32, + /// A 32 bit time interval before the zone should be refreshed. + pub refresh: i32, + /// A 32 bit time interval that should elapse before a failed refresh should be retried. + pub retry: i32, + /// A 32 bit time value that specifies the upper limit on the time interval that can elapse before the zone is no longer authoritative. + pub expire: i32, + /// The unsigned 32 bit minimum TTL field that should be exported with any RR from this zone. + pub minimum: u32, +} + +impl RR for SOA<'_> { + const TYPE_CODE: u16 = 6; +} + +impl SOA<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> SOA<'b> { + SOA { + mname: self.mname.into_owned(), + rname: self.rname.into_owned(), + serial: self.serial, + refresh: self.refresh, + retry: self.retry, + expire: self.expire, + minimum: self.minimum, + } + } + + fn write_common(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.serial.to_be_bytes())?; + out.write_all(&self.refresh.to_be_bytes())?; + out.write_all(&self.retry.to_be_bytes())?; + out.write_all(&self.expire.to_be_bytes())?; + out.write_all(&self.minimum.to_be_bytes())?; + + Ok(()) + } +} + +impl<'a> WireFormat<'a> for SOA<'a> { + const MINIMUM_LEN: usize = 20; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let mname = Name::parse(data)?; + let rname = Name::parse(data)?; + + let serial = data.get_u32()?; + let refresh = data.get_i32()?; + let retry = data.get_i32()?; + let expire = data.get_i32()?; + let minimum = data.get_u32()?; + + Ok(Self { + mname, + rname, + serial, + refresh, + retry, + expire, + minimum, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.mname.write_to(out)?; + self.rname.write_to(out)?; + self.write_common(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.mname.write_compressed_to(out, name_refs)?; + self.rname.write_compressed_to(out, name_refs)?; + self.write_common(out) + } + + fn len(&self) -> usize { + self.mname.len() + self.rname.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + + use super::*; + #[test] + fn parse_and_write_soa() { + let soa = SOA { + mname: Name::new("mname.soa.com").unwrap(), + rname: Name::new("rname.soa.com").unwrap(), + serial: 1, + refresh: 2, + retry: 3, + expire: 4, + minimum: 5, + }; + + let mut data = Vec::new(); + assert!(soa.write_to(&mut data).is_ok()); + + let soa = SOA::parse(&mut data[..].into()); + assert!(soa.is_ok()); + let soa = soa.unwrap(); + + assert_eq!(data.len(), soa.len()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_soa_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/SOA.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::SOA(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.mname, "VENERA.sample".try_into()?); + assert_eq!( + sample_rdata.rname, + [ + crate::Label::new_unchecked(b"Action.domains"), + crate::Label::new_unchecked(b"sample") + ] + .into() + ); + assert_eq!(sample_rdata.serial, 20); + assert_eq!(sample_rdata.refresh, 7200); + assert_eq!(sample_rdata.retry, 600); + assert_eq!(sample_rdata.expire, 3600000); + assert_eq!(sample_rdata.minimum, 60); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/srv.rs b/vendor/simple-dns/src/dns/rdata/srv.rs new file mode 100644 index 00000000..9d096f40 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/srv.rs @@ -0,0 +1,137 @@ +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Write, Name}; + +use super::RR; + +/// SRV records specifies the location of the server(s) for a specific protocol and domain. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct SRV<'a> { + /// The priority of this target host. + /// A client MUST attempt to contact the target host with the lowest-numbered priority it can + /// reach; target hosts with the same priority SHOULD be tried in an order defined by the weight field. + pub priority: u16, + /// A server selection mechanism. + /// The weight field specifies arelative weight for entries with the same priority. + /// Larger weights SHOULD be given a proportionately higher probability of being selected. + pub weight: u16, + /// The port on this target host of this service + pub port: u16, + /// The domain name of the target host + pub target: Name<'a>, +} + +impl RR for SRV<'_> { + const TYPE_CODE: u16 = 33; +} + +impl SRV<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> SRV<'b> { + SRV { + priority: self.priority, + weight: self.weight, + port: self.port, + target: self.target.into_owned(), + } + } +} + +impl<'a> WireFormat<'a> for SRV<'a> { + const MINIMUM_LEN: usize = 6; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let priority = data.get_u16()?; + let weight = data.get_u16()?; + let port = data.get_u16()?; + let target = Name::parse(data)?; + + Ok(Self { + priority, + weight, + port, + target, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.priority.to_be_bytes())?; + out.write_all(&self.weight.to_be_bytes())?; + out.write_all(&self.port.to_be_bytes())?; + + self.target.write_to(out) + } + + fn len(&self) -> usize { + self.target.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::Vec; + + #[test] + fn parse_and_write_srv() { + let srv = SRV { + priority: 1, + weight: 2, + port: 3, + target: Name::new("_srv._tcp.example.com").unwrap(), + }; + + let mut bytes = Vec::new(); + assert!(srv.write_to(&mut bytes).is_ok()); + + let srv = SRV::parse(&mut bytes[..].into()); + assert!(srv.is_ok()); + let srv = srv.unwrap(); + + assert_eq!(1, srv.priority); + assert_eq!(2, srv.weight); + assert_eq!(3, srv.port); + assert_eq!(bytes.len(), srv.len()); + } + + #[test] + fn srv_should_not_be_compressed() { + use crate::lib::Cursor; + + let srv = SRV { + priority: 1, + weight: 2, + port: 3, + target: Name::new("_srv._tcp.example.com").unwrap(), + }; + + let mut plain = Vec::new(); + let mut compressed = Cursor::new(Vec::new()); + let mut names = Default::default(); + + assert!(srv.write_to(&mut plain).is_ok()); + assert!(srv.write_compressed_to(&mut compressed, &mut names).is_ok()); + + assert_eq!(plain, compressed.into_inner()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/SRV.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::SRV(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.priority, 65535); + assert_eq!(sample_rdata.weight, 65535); + assert_eq!(sample_rdata.port, 65535); + assert_eq!(sample_rdata.target, "old-slow-box.sample".try_into()?); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/svcb.rs b/vendor/simple-dns/src/dns/rdata/svcb.rs new file mode 100644 index 00000000..49c93bf6 --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/svcb.rs @@ -0,0 +1,481 @@ +use crate::lib::{BTreeMap, BTreeSet, Cow, Vec}; +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat, lib::Write, CharacterString, Name}; + +use super::RR; + +/// The SVCB DNS RR type is used to locate alternative endpoints for a service. +/// [RFC 9460](https://datatracker.ietf.org/doc/html/rfc9460). +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct SVCB<'a> { + /// The priority of this record (relative to others, with lower values preferred). + /// + /// A value of 0 indicates AliasMode. + pub priority: u16, + + /// The domain name of either the alias target (for AliasMode) + /// or the alternative endpoint (for ServiceMode). + pub target: Name<'a>, + + /// A list of key=value pairs describing the alternative endpoint at `target`. + params: BTreeMap>, +} + +impl RR for SVCB<'_> { + const TYPE_CODE: u16 = 64; +} + +impl<'a> SVCB<'a> { + /// Creates a new `SVCB` instance with no parameters. + pub fn new(priority: u16, target: Name<'a>) -> Self { + Self { + priority, + target, + params: BTreeMap::new(), + } + } + + /// Sets a parameter, replacing any previous value. + pub fn set_param(&mut self, param: SVCParam<'a>) { + self.params.insert(param.key_code(), param); + } + + /// Same as [`Self::set_param`], but returns `self` for chaining. + pub fn with_param(mut self, param: SVCParam<'a>) -> Self { + self.set_param(param); + self + } + + /// Sets the "mandatory" parameter. + /// + /// If `keys` is empty, this method does nothing. + pub fn set_mandatory(&mut self, keys: impl Iterator) { + let keys: BTreeSet<_> = keys.collect(); + if keys.is_empty() { + return; + } + + self.set_param(SVCParam::Mandatory(keys)); + } + + /// Sets the "alpn" parameter. + /// + /// if `alpn_ids` is empty, this method does nothing. + pub fn set_alpn(&mut self, alpn_ids: &[CharacterString<'a>]) { + if alpn_ids.is_empty() { + return; + } + + self.set_param(SVCParam::Alpn(alpn_ids.into())); + } + + /// Sets the "no-default-alpn" parameter. + pub fn set_no_default_alpn(&mut self) { + self.set_param(SVCParam::NoDefaultAlpn); + } + + /// Sets the "port" parameter. + pub fn set_port(&mut self, port: u16) { + self.set_param(SVCParam::Port(port)); + } + + /// Sets the "ipv4hint" parameter. + /// + /// if `ips` is empty, this method does nothing. + pub fn set_ipv4hint(&mut self, ips: &[u32]) { + if ips.is_empty() { + return; + } + + self.set_param(SVCParam::Ipv4Hint(ips.into())); + } + + /// Sets the "ipv6hint" parameter. + /// + /// if `ips` is empty, this method does nothing + pub fn set_ipv6hint(&mut self, ips: &[u128]) { + if ips.is_empty() { + return; + } + + self.set_param(SVCParam::Ipv6Hint(ips.into())) + } + + /// Gets a read-only reference to the [`SVCParam`] + /// + /// Returns `None` if the key does not exist. + pub fn get_param(&'a self, key: u16) -> Option<&'a SVCParam<'a>> { + self.params.get(&key) + } + + /// Iterates over all parameters. + pub fn iter_params(&self) -> impl Iterator> { + self.params.values() + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> SVCB<'b> { + SVCB { + priority: self.priority, + target: self.target.into_owned(), + params: self + .params + .into_iter() + .map(|(k, v)| (k, v.into_owned())) + .collect(), + } + } +} + +impl<'a> WireFormat<'a> for SVCB<'a> { + const MINIMUM_LEN: usize = 2; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let priority = data.get_u16()?; + + let target = Name::parse(data)?; + let mut params = BTreeMap::new(); + + let mut previous_key: Option = None; + while data.has_remaining() { + let param = SVCParam::parse(data)?; + let key = param.key_code(); + + if let Some(p_key) = previous_key { + if key <= p_key { + return Err(crate::SimpleDnsError::InvalidDnsPacket); + } + } + + previous_key = Some(key); + params.insert(key, param); + } + Ok(Self { + priority, + target, + params, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.priority.to_be_bytes())?; + self.target.write_to(out)?; + for param in self.params.values() { + param.write_to(out)?; + } + Ok(()) + } + + // NOT implementing `write_compressed_to`, + // RFC9460 §2.2 specifically mentioned the TargetName is *uncompressed*. + + fn len(&self) -> usize { + self.target.len() + self.params.values().map(|p| p.len()).sum::() + Self::MINIMUM_LEN + } +} + +/// The SVC Param section of the SVCB DNS RR type. +/// [RFC 9460](https://datatracker.ietf.org/doc/html/rfc9460). +/// +/// Known parameters are defined as variants of this enum and properly parsed. +/// Unknown parameters are stored as [Self::Unknown] variant. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum SVCParam<'a> { + /// Mandatory keys in this RR. Key Code 0. + Mandatory(BTreeSet), + + /// Additional supported protocols. Key Code 1. + Alpn(Vec>), + + /// No support for default protocol. Key Code 2. + NoDefaultAlpn, + + /// Port for alternative endpoint. Key Code 3. + Port(u16), + + /// IPv4 address hints. Key Code 4. + Ipv4Hint(Vec), + + /// Encrypted ClientHello (ECH) configuration. Key Code 5. + Ech(Cow<'a, [u8]>), + + /// IPv6 address hints. Key Code 6. + Ipv6Hint(Vec), + + /// Reserved for invalid keys. Key Code 65535. + InvalidKey, + + /// Unknown key format. + Unknown(u16, Cow<'a, [u8]>), +} + +impl SVCParam<'_> { + /// Returns the key code of the parameter + pub fn key_code(&self) -> u16 { + match self { + SVCParam::Mandatory(_) => 0, + SVCParam::Alpn(_) => 1, + SVCParam::NoDefaultAlpn => 2, + SVCParam::Port(_) => 3, + SVCParam::Ipv4Hint(_) => 4, + SVCParam::Ech(_) => 5, + SVCParam::Ipv6Hint(_) => 6, + SVCParam::InvalidKey => 65535, + SVCParam::Unknown(key, _) => *key, + } + } + + /// Transforms the inner data into its owned + pub fn into_owned<'b>(self) -> SVCParam<'b> { + match self { + SVCParam::Mandatory(keys) => SVCParam::Mandatory(keys), + SVCParam::Alpn(alpns) => { + SVCParam::Alpn(alpns.into_iter().map(|a| a.into_owned()).collect()) + } + SVCParam::NoDefaultAlpn => SVCParam::NoDefaultAlpn, + SVCParam::Port(port) => SVCParam::Port(port), + SVCParam::Ipv4Hint(ips) => SVCParam::Ipv4Hint(ips), + SVCParam::Ech(ech) => SVCParam::Ech(ech.into_owned().into()), + SVCParam::Ipv6Hint(ips) => SVCParam::Ipv6Hint(ips), + SVCParam::InvalidKey => SVCParam::InvalidKey, + SVCParam::Unknown(key, value) => SVCParam::Unknown(key, value.into_owned().into()), + } + } +} + +impl<'a> WireFormat<'a> for SVCParam<'a> { + const MINIMUM_LEN: usize = 4; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let key = data.get_u16()?; + let len = data.get_u16()? as usize; + + let mut data = data.new_limited_to(len)?; + match key { + 0 => { + let mut keys = BTreeSet::new(); + while data.has_remaining() { + keys.insert(data.get_u16()?); + } + Ok(SVCParam::Mandatory(keys)) + } + 1 => { + let mut alpns = Vec::new(); + while data.has_remaining() { + alpns.push(CharacterString::parse(&mut data)?); + } + Ok(SVCParam::Alpn(alpns)) + } + 2 => Ok(SVCParam::NoDefaultAlpn), + 3 => Ok(SVCParam::Port(data.get_u16()?)), + 4 => { + let mut ips = Vec::new(); + while data.has_remaining() { + ips.push(data.get_u32()?); + } + Ok(SVCParam::Ipv4Hint(ips)) + } + 5 => { + let len = data.get_u16()? as usize; + let data = data.get_remaining(); + if data.len() != len { + Err(crate::SimpleDnsError::InvalidDnsPacket) + } else { + Ok(SVCParam::Ech(Cow::Borrowed(data))) + } + } + 6 => { + let mut ips = Vec::new(); + while data.has_remaining() { + ips.push(data.get_u128()?); + } + Ok(SVCParam::Ipv6Hint(ips)) + } + _ => { + let value = Cow::Borrowed(data.get_remaining()); + Ok(SVCParam::Unknown(key, value)) + } + } + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.key_code().to_be_bytes())?; + out.write_all(&(self.len() as u16 - 4).to_be_bytes())?; + + match self { + SVCParam::Mandatory(keys) => { + for key in keys { + out.write_all(&key.to_be_bytes())?; + } + } + SVCParam::Alpn(alpns) => { + for alpn in alpns.iter() { + alpn.write_to(out)?; + } + } + SVCParam::NoDefaultAlpn => {} + SVCParam::Port(port) => { + out.write_all(&port.to_be_bytes())?; + } + SVCParam::Ipv4Hint(ips) => { + for ip in ips.iter() { + out.write_all(&ip.to_be_bytes())?; + } + } + SVCParam::Ech(ech) => { + out.write_all(&(ech.len() as u16).to_be_bytes())?; + out.write_all(ech)?; + } + SVCParam::Ipv6Hint(ips) => { + for ip in ips.iter() { + out.write_all(&ip.to_be_bytes())?; + } + } + SVCParam::Unknown(_, value) => { + out.write_all(value)?; + } + _ => return Err(crate::SimpleDnsError::InvalidDnsPacket), + }; + + Ok(()) + } + + fn len(&self) -> usize { + // key + param len + param value len + Self::MINIMUM_LEN + + match self { + SVCParam::Mandatory(keys) => keys.len() * 2, + SVCParam::Alpn(alpns) => alpns.iter().map(|a| a.len()).sum(), + SVCParam::NoDefaultAlpn => 0, + SVCParam::Port(_) => 2, + SVCParam::Ipv4Hint(ips) => ips.len() * 4, + SVCParam::Ech(ech) => 2 + ech.len(), + SVCParam::Ipv6Hint(ips) => ips.len() * 16, + SVCParam::Unknown(_, value) => value.len(), + _ => 0, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + // Copy of the answer from `dig crypto.cloudflare.com -t HTTPS`. + let sample_file = std::fs::read("samples/zonefile/HTTPS.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::HTTPS(rdata) => rdata, + _ => unreachable!(), + }; + + let mut expected_rdata = SVCB::new(1, Name::new_unchecked("")); + expected_rdata.set_alpn(&["http/1.1".try_into()?, "h2".try_into()?]); + expected_rdata.set_ipv4hint(&[0xa2_9f_89_55, 0xa2_9f_8a_55]); + expected_rdata.set_param(SVCParam::Ech( + b"\xfe\x0d\x00\x41\x44\x00\x20\x00\x20\x1a\xd1\x4d\x5c\xa9\x52\xda\ + \x88\x18\xae\xaf\xd7\xc6\xc8\x7d\x47\xb4\xb3\x45\x7f\x8e\x58\xbc\ + \x87\xb8\x95\xfc\xb3\xde\x1b\x34\x33\x00\x04\x00\x01\x00\x01\x00\ + \x12cloudflare-ech.com\x00\x00" + .into(), + )); + expected_rdata.set_ipv6hint(&[ + 0x2606_4700_0007_0000_0000_0000_a29f_8955, + 0x2606_4700_0007_0000_0000_0000_a29f_8a55, + ]); + + assert_eq!(*sample_rdata, expected_rdata); + + assert_eq!( + sample_rdata.get_param(1), + Some(&SVCParam::Alpn(vec![ + "http/1.1".try_into().unwrap(), + "h2".try_into().unwrap() + ])) + ); + assert_eq!(sample_rdata.get_param(3), None); + + Ok(()) + } + + #[test] + fn parse_and_write_svcb() { + // Test vectors are taken from Appendix D. + // + let tests: &[(&str, &[u8], SVCB<'_>)] = &[ + ( + "D.1. AliasMode", + b"\x00\x00\x03foo\x07example\x03com\x00", + SVCB::new(0, Name::new_unchecked("foo.example.com")), + ), + ( + "D.2.3. TargetName Is '.'", + b"\x00\x01\x00", + SVCB::new(1, Name::new_unchecked("")), + ), + ( + "D.2.4. Specified a Port", + b"\x00\x10\x03foo\x07example\x03com\x00\x00\x03\x00\x02\x00\x35", + { + let mut svcb = SVCB::new(16, Name::new_unchecked("foo.example.com")); + svcb.set_port(53); + svcb + } + ), + ( + "D.2.6. A Generic Key and Quoted Value with a Decimal Escape", + b"\x00\x01\x03foo\x07example\x03com\x00\x02\x9b\x00\x09hello\xd2qoo", + { + let svcb = SVCB::new(1, Name::new_unchecked("foo.example.com")).with_param(SVCParam::Unknown(667, b"hello\xd2qoo"[..].into())); + svcb + } + ), + ( + "D.2.7. Two Quoted IPv6 Hints", + b"\x00\x01\x03foo\x07example\x03com\x00\x00\x06\x00\x20\ + \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\ + \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01", + { + let mut svcb = SVCB::new(1, Name::new_unchecked("foo.example.com")); + svcb.set_ipv6hint(&[ + 0x2001_0db8_0000_0000_0000_0000_0000_0001, + 0x2001_0db8_0000_0000_0000_0000_0053_0001, + ]); + svcb + }, + ), + ( + "D.2.10. SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format", + b"\x00\x10\x03foo\x07example\x03org\x00\ + \x00\x00\x00\x04\x00\x01\x00\x04\ + \x00\x01\x00\x09\x02h2\x05h3-19\ + \x00\x04\x00\x04\xc0\x00\x02\x01", + { + let mut svcb = SVCB::new(16, Name::new_unchecked("foo.example.org")); + svcb.set_alpn(&["h2".try_into().unwrap(), "h3-19".try_into().unwrap()]); + svcb.set_mandatory([1, 4].into_iter()); + svcb.set_ipv4hint(&[0xc0_00_02_01]); + svcb + }, + ), + ]; + + for (name, expected_bytes, svcb) in tests { + let mut data = Vec::new(); + svcb.write_to(&mut data).unwrap(); + assert_eq!(expected_bytes, &data, "Test {name}"); + + let svcb2 = SVCB::parse(&mut data[..].into()).unwrap(); + assert_eq!(svcb, &svcb2, "Test {name}"); + } + } +} diff --git a/vendor/simple-dns/src/dns/rdata/txt.rs b/vendor/simple-dns/src/dns/rdata/txt.rs new file mode 100644 index 00000000..a1121a9a --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/txt.rs @@ -0,0 +1,342 @@ +use crate::CharacterString; +use crate::{ + dns::{WireFormat, MAX_CHARACTER_STRING_LENGTH}, + lib::Write, + lib::{vec, FromUtf8Error, String, Vec}, +}; + +use super::RR; + +/// Represents a TXT Resource Record +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct TXT<'a> { + strings: Vec>, + size: usize, +} + +impl RR for TXT<'_> { + const TYPE_CODE: u16 = 16; +} + +impl Default for TXT<'_> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> TXT<'a> { + /// Creates a new empty TXT Record + pub fn new() -> Self { + Self { + strings: vec![], + size: 0, + } + } + + /// Add `char_string` to this TXT record as a validated [`CharacterString`](`CharacterString`) + pub fn add_string(&mut self, char_string: &'a str) -> crate::Result<()> { + self.add_char_string(char_string.try_into()?); + Ok(()) + } + + /// Add `char_string` to this TXT record + pub fn add_char_string(&mut self, char_string: CharacterString<'a>) { + self.size += char_string.len(); + self.strings.push(char_string); + } + + /// Add `char_string` to this TXT record as a validated [`CharacterString`](`CharacterString`), consuming and returning Self + pub fn with_string(mut self, char_string: &'a str) -> crate::Result { + self.add_char_string(char_string.try_into()?); + Ok(self) + } + + /// Add `char_string` to this TXT record, consuming and returning Self + pub fn with_char_string(mut self, char_string: CharacterString<'a>) -> Self { + self.add_char_string(char_string); + self + } + + /// Returns parsed attributes from this TXT Record as bytes, valid formats are: + /// - key=value + /// - key= + /// - key + pub fn iter_raw(&self) -> impl Iterator)> { + self.strings.iter().filter_map(|char_str| { + let mut splited = char_str.data.splitn(2, |c| *c == b'='); + let key = splited.next()?; + let value = splited.next(); + Some((key, value)) + }) + } + + // FIXME: remove the std feature once the HashMap is sorted out + /// Returns parsed attributes from this TXT Record, valid formats are: + /// - key=value + /// - key= + /// - key + /// + /// If a key is duplicated, only the first one will be considered + #[cfg(feature = "std")] + pub fn attributes(&self) -> crate::lib::HashMap> { + let mut attributes = crate::lib::HashMap::new(); + let iter = self.iter_raw().filter_map(|(key, value)| { + let key = match crate::lib::str::from_utf8(key) { + Ok(key) => key.to_owned(), + Err(_) => return None, + }; + + let value = match value { + Some(value) if !value.is_empty() => match crate::lib::str::from_utf8(value) { + Ok(v) => Some(v.to_owned()), + Err(_) => Some(String::new()), + }, + Some(_) => Some(String::new()), + _ => None, + }; + + Some((key, value)) + }); + + for (key, value) in iter { + attributes.entry(key).or_insert(value); + } + + attributes + } + + /// Similar to [`attributes()`](TXT::attributes) but it parses the full TXT record as a single string, + /// instead of expecting each attribute to be a separate [`CharacterString`](`CharacterString`) + #[cfg(feature = "std")] + pub fn long_attributes(self) -> crate::Result>> { + let mut attributes = crate::lib::HashMap::new(); + + let full_string: String = match self.try_into() { + Ok(string) => string, + Err(err) => return Err(crate::SimpleDnsError::InvalidUtf8String(err)), + }; + + let parts = full_string.split(|c| (c as u8) == b';'); + + for part in parts { + let key_value = part.splitn(2, |c| (c as u8) == b'=').collect::>(); + + let key = key_value[0]; + + let value = match key_value.len() > 1 { + true => Some(key_value[1].to_owned()), + _ => None, + }; + + if !key.is_empty() { + attributes.entry(key.to_owned()).or_insert(value); + } + } + + Ok(attributes) + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> TXT<'b> { + TXT { + strings: self.strings.into_iter().map(|s| s.into_owned()).collect(), + size: self.size, + } + } +} + +#[cfg(feature = "std")] +impl TryFrom>> for TXT<'_> { + type Error = crate::SimpleDnsError; + + fn try_from(value: crate::lib::HashMap>) -> Result { + let mut txt = TXT::new(); + for (key, value) in value { + match value { + Some(value) => { + txt.add_char_string(format!("{key}={value}").try_into()?); + } + None => txt.add_char_string(key.try_into()?), + } + } + Ok(txt) + } +} + +impl<'a> TryFrom<&'a str> for TXT<'a> { + type Error = crate::SimpleDnsError; + + fn try_from(value: &'a str) -> Result { + let mut txt = TXT::new(); + for v in value.as_bytes().chunks(MAX_CHARACTER_STRING_LENGTH - 1) { + txt.add_char_string(CharacterString::new(v)?); + } + Ok(txt) + } +} + +impl<'a> TryFrom> for String { + type Error = FromUtf8Error; + + fn try_from(val: TXT<'a>) -> Result { + let init = Vec::with_capacity(val.len()); + + let bytes = val.strings.into_iter().fold(init, |mut acc, val| { + acc.extend(val.data.as_ref()); + acc + }); + String::from_utf8(bytes) + } +} + +impl<'a> WireFormat<'a> for TXT<'a> { + const MINIMUM_LEN: usize = 1; + + fn parse(data: &mut crate::bytes_buffer::BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let mut strings = Vec::new(); + let mut size = 0; + + while data.has_remaining() { + let char_str = CharacterString::parse(data)?; + size += char_str.len(); + strings.push(char_str); + } + + Ok(Self { strings, size }) + } + + fn len(&self) -> usize { + if self.strings.is_empty() { + Self::MINIMUM_LEN + } else { + self.size + } + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + if self.strings.is_empty() { + out.write_all(&[0])?; + } else { + for string in &self.strings { + string.write_to(out)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lib::Error; + + #[test] + pub fn parse_and_write_txt() -> Result<(), crate::lib::Box> { + let mut out = vec![]; + let txt = TXT::new() + .with_char_string("version=0.1".try_into()?) + .with_char_string("proto=123".try_into()?); + + txt.write_to(&mut out)?; + assert_eq!(out.len(), txt.len()); + + let txt2 = TXT::parse(&mut out[..].into())?; + assert_eq!(2, txt2.strings.len()); + assert_eq!(txt.strings[0], txt2.strings[0]); + assert_eq!(txt.strings[1], txt2.strings[1]); + + Ok(()) + } + + #[test] + pub fn iter_raw() -> Result<(), crate::lib::Box> { + let txt = TXT::new() + .with_string("version=0.1")? + .with_string("flag")? + .with_string("with_eq=eq=")? + .with_string("version=dup")? + .with_string("empty=")?; + + assert_eq!( + txt.iter_raw().collect::>(), + vec![ + ("version".as_bytes(), Some("0.1".as_bytes())), + ("flag".as_bytes(), None), + ("with_eq".as_bytes(), Some("eq=".as_bytes())), + ("version".as_bytes(), Some("dup".as_bytes())), + ("empty".as_bytes(), Some("".as_bytes())) + ] + ); + Ok(()) + } + + #[test] + #[cfg(feature = "std")] + pub fn get_attributes() -> Result<(), Box> { + let attributes = TXT::new() + .with_string("version=0.1")? + .with_string("flag")? + .with_string("with_eq=eq=")? + .with_string("version=dup")? + .with_string("empty=")? + .attributes(); + + assert_eq!(4, attributes.len()); + assert_eq!(Some("0.1".to_owned()), attributes["version"]); + assert_eq!(Some("eq=".to_owned()), attributes["with_eq"]); + assert_eq!(Some(String::new()), attributes["empty"]); + assert_eq!(None, attributes["flag"]); + + Ok(()) + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::{rdata::RData, ResourceRecord}; + let sample_file = std::fs::read("samples/zonefile/TXT.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::TXT(rdata) => rdata, + _ => unreachable!(), + }; + + let strings = vec!["\"foo\nbar\"".try_into()?]; + assert_eq!(sample_rdata.strings, strings); + + Ok(()) + } + + #[test] + fn write_and_parse_large_txt() -> Result<(), crate::lib::Box> { + let string = "X".repeat(1000); + let txt: TXT = string.as_str().try_into()?; + + let mut bytes = Vec::new(); + assert!(txt.write_to(&mut bytes).is_ok()); + + let parsed_txt = TXT::parse(&mut bytes[..].into())?; + let parsed_string: String = parsed_txt.try_into()?; + + assert_eq!(parsed_string, string); + + Ok(()) + } + + #[test] + #[cfg(feature = "std")] + fn write_and_parse_large_attributes() -> Result<(), Box> { + let big_value = "f".repeat(1000); + + let string = format!("foo={big_value};;flag;bar={big_value}"); + let txt: TXT = string.as_str().try_into()?; + let attributes = txt.long_attributes()?; + + assert_eq!(Some(big_value.to_owned()), attributes["bar"]); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/wks.rs b/vendor/simple-dns/src/dns/rdata/wks.rs new file mode 100644 index 00000000..24abbd4e --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/wks.rs @@ -0,0 +1,89 @@ +use crate::lib::Cow; +use crate::lib::Write; +use crate::{bytes_buffer::BytesBuffer, dns::WireFormat}; + +use super::RR; + +/// The WKS record is used to describe the well known services supported by a particular protocol on a particular internet address. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct WKS<'a> { + /// An 32 bit Internet address + pub address: u32, + /// An 8 bit IP protocol number + pub protocol: u8, + /// A variable length bit map. The bit map must be a multiple of 8 bits long. + pub bit_map: Cow<'a, [u8]>, +} + +impl RR for WKS<'_> { + const TYPE_CODE: u16 = 11; +} + +impl WKS<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> WKS<'b> { + WKS { + address: self.address, + protocol: self.protocol, + bit_map: self.bit_map.into_owned().into(), + } + } +} + +impl<'a> WireFormat<'a> for WKS<'a> { + const MINIMUM_LEN: usize = 5; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let address = data.get_u32()?; + let protocol = data.get_u8()?; + let bit_map = Cow::Borrowed(data.get_remaining()); + + Ok(Self { + address, + protocol, + bit_map, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.address.to_be_bytes())?; + out.write_all(&[self.protocol])?; + out.write_all(&self.bit_map)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.bit_map.len() + Self::MINIMUM_LEN + } +} + +#[cfg(test)] +mod tests { + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), Box> { + use crate::dns::WireFormat; + use crate::lib::*; + use crate::{rdata::RData, ResourceRecord}; + + let sample_file = std::fs::read("samples/zonefile/WKS.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::WKS(rdata) => rdata, + _ => unreachable!(), + }; + + let sample_ip: u32 = "10.0.0.1".parse::()?.into(); + + assert_eq!(sample_rdata.address, sample_ip); + assert_eq!(sample_rdata.protocol, 6); + assert_eq!(sample_rdata.bit_map, vec![224, 0, 5]); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/rdata/zonemd.rs b/vendor/simple-dns/src/dns/rdata/zonemd.rs new file mode 100644 index 00000000..5aec730c --- /dev/null +++ b/vendor/simple-dns/src/dns/rdata/zonemd.rs @@ -0,0 +1,120 @@ +use crate::{ + bytes_buffer::BytesBuffer, + dns::WireFormat, + lib::{Cow, Write}, +}; + +use super::RR; + +/// A ZoneMD record see [rfc8976](https://www.rfc-editor.org/rfc/rfc8976.html) +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct ZONEMD<'a> { + /// The serial number of the zone's SOA record + pub serial: u32, + /// The scheme in which data is hashed + pub scheme: u8, + /// The hashing algorithm ID to use (see [rfc8976](https://www.rfc-editor.org/rfc/rfc8976.html#name-the-hash-algorithm-field)) + pub algorithm: u8, + /// The output data of the hash algorithm. + pub digest: Cow<'a, [u8]>, +} + +impl RR for ZONEMD<'_> { + const TYPE_CODE: u16 = 63; +} + +impl<'a> WireFormat<'a> for ZONEMD<'a> { + const MINIMUM_LEN: usize = 6; + + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let serial = data.get_u32()?; + let scheme = data.get_u8()?; + let algorithm = data.get_u8()?; + let digest = Cow::Borrowed(data.get_remaining()); + + Ok(Self { + serial, + scheme, + algorithm, + digest, + }) + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&self.serial.to_be_bytes())?; + out.write_all(&[self.scheme])?; + out.write_all(&[self.algorithm])?; + out.write_all(&self.digest)?; + + Ok(()) + } + + fn len(&self) -> usize { + self.digest.len() + Self::MINIMUM_LEN + } +} + +impl ZONEMD<'_> { + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> ZONEMD<'b> { + ZONEMD { + scheme: self.scheme, + serial: self.serial, + algorithm: self.algorithm, + digest: self.digest.into_owned().into(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::lib::Vec; + use crate::rdata::ZONEMD; + + use super::*; + + #[test] + fn parse_and_write_srv() { + let zonemd = ZONEMD { + serial: 1, + scheme: 2, + algorithm: 3, + digest: Cow::Borrowed(&[4, 5, 6]), + }; + + let mut bytes = Vec::new(); + assert!(zonemd.write_to(&mut bytes).is_ok()); + + let zonemd = ZONEMD::parse(&mut bytes[..].into()); + assert!(zonemd.is_ok()); + let zonemd = zonemd.unwrap(); + + assert_eq!(zonemd.serial, 1); + assert_eq!(zonemd.scheme, 2); + assert_eq!(zonemd.algorithm, 3); + assert_eq!(*zonemd.digest, *b"\x04\x05\x06"); + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample() -> Result<(), crate::lib::Box> { + use crate::{rdata::RData, ResourceRecord}; + + let sample_file = std::fs::read("samples/zonefile/ZONEMD.sample")?; + + let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata { + RData::ZONEMD(rdata) => rdata, + _ => unreachable!(), + }; + + assert_eq!(sample_rdata.serial, 2018031500); + assert_eq!(sample_rdata.scheme, 1); + assert_eq!(sample_rdata.algorithm, 1); + assert_eq!(*sample_rdata.digest, *b"\xFE\xBE\x3D\x4C\xE2\xEC\x2F\xFA\x4B\xA9\x9D\x46\xCD\x69\xD6\xD2\x97\x11\xE5\x52\x17\x05\x7B\xEE\x7E\xB1\xA7\xB6\x41\xA4\x7B\xA7\xFE\xD2\xDD\x5B\x97\xAE\x49\x9F\xAF\xA4\xF2\x2C\x6B\xD6\x47\xDE"); + + Ok(()) + } +} diff --git a/vendor/simple-dns/src/dns/resource_record.rs b/vendor/simple-dns/src/dns/resource_record.rs new file mode 100644 index 00000000..0af68adc --- /dev/null +++ b/vendor/simple-dns/src/dns/resource_record.rs @@ -0,0 +1,409 @@ +use crate::{ + bytes_buffer::BytesBuffer, + lib::{Hash, Hasher, Seek, SeekFrom, Write}, + QCLASS, QTYPE, +}; + +use super::{rdata::RData, Name, WireFormat, CLASS, TYPE}; +use core::fmt::Debug; + +mod flag { + pub const CACHE_FLUSH: u16 = 0b1000_0000_0000_0000; +} +/// Resource Records are used to represent the answer, authority, and additional sections in DNS packets. +#[derive(Debug, Eq, Clone)] +pub struct ResourceRecord<'a> { + /// A [`Name`] to which this resource record pertains. + pub name: Name<'a>, + /// A [`CLASS`] that defines the class of the rdata field + pub class: CLASS, + /// The time interval (in seconds) that the resource record may becached before it should be discarded. + /// Zero values are interpreted to mean that the RR can only be used for the transaction in progress, and should not be cached. + pub ttl: u32, + /// A [`RData`] with the contents of this resource record + pub rdata: RData<'a>, + + /// Indicates if this RR is a cache flush + pub cache_flush: bool, +} + +impl<'a> ResourceRecord<'a> { + /// Creates a new ResourceRecord + pub fn new(name: Name<'a>, class: CLASS, ttl: u32, rdata: RData<'a>) -> Self { + Self { + name, + class, + ttl, + rdata, + cache_flush: false, + } + } + + /// Consume self and change the cache_flush bit + pub fn with_cache_flush(mut self, cache_flush: bool) -> Self { + self.cache_flush = cache_flush; + self + } + + /// Returns a cloned self with cache_flush = true + pub fn to_cache_flush_record(&self) -> Self { + self.clone().with_cache_flush(true) + } + + /// Return true if current resource match given query class + pub fn match_qclass(&self, qclass: QCLASS) -> bool { + match qclass { + QCLASS::CLASS(class) => class == self.class, + QCLASS::ANY => true, + } + } + + /// Return true if current resource match given query type + pub fn match_qtype(&self, qtype: QTYPE) -> bool { + let type_code = self.rdata.type_code(); + match qtype { + QTYPE::ANY => true, + QTYPE::IXFR => false, + QTYPE::AXFR => true, // TODO: figure out what to do here + QTYPE::MAILB => type_code == TYPE::MR || type_code == TYPE::MB || type_code == TYPE::MG, + QTYPE::MAILA => type_code == TYPE::MX, + QTYPE::TYPE(ty) => ty == type_code, + } + } + + /// Transforms the inner data into its owned type + pub fn into_owned<'b>(self) -> ResourceRecord<'b> { + ResourceRecord { + name: self.name.into_owned(), + class: self.class, + ttl: self.ttl, + rdata: self.rdata.into_owned(), + cache_flush: self.cache_flush, + } + } + + fn write_common(&self, out: &mut T) -> crate::Result<()> { + out.write_all(&u16::from(self.rdata.type_code()).to_be_bytes())?; + + if let RData::OPT(ref opt) = self.rdata { + out.write_all(&opt.udp_packet_size.to_be_bytes())?; + } else { + let class = if self.cache_flush { + ((self.class as u16) | flag::CACHE_FLUSH).to_be_bytes() + } else { + (self.class as u16).to_be_bytes() + }; + + out.write_all(&class)?; + } + + out.write_all(&self.ttl.to_be_bytes()) + } +} + +impl<'a> WireFormat<'a> for ResourceRecord<'a> { + const MINIMUM_LEN: usize = 10; + + // Disable redundant length check. + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized, + { + let name = Name::parse(data)?; + + let class_value = data.peek_u16_in(2)?; + let ttl = data.peek_u32_in(4)?; + + let rdata = RData::parse(data)?; + + if rdata.type_code() == TYPE::OPT { + Ok(Self { + name, + class: CLASS::IN, + ttl, + rdata, + cache_flush: false, + }) + } else { + let cache_flush = class_value & flag::CACHE_FLUSH == flag::CACHE_FLUSH; + let class = (class_value & !flag::CACHE_FLUSH).try_into()?; + + Ok(Self { + name, + class, + ttl, + rdata, + cache_flush, + }) + } + } + + fn len(&self) -> usize { + self.name.len() + self.rdata.len() + Self::MINIMUM_LEN + } + + fn write_to(&self, out: &mut T) -> crate::Result<()> { + self.name.write_to(out)?; + self.write_common(out)?; + out.write_all(&(self.rdata.len() as u16).to_be_bytes())?; + self.rdata.write_to(out) + } + + fn write_compressed_to( + &'a self, + out: &mut T, + name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.name.write_compressed_to(out, name_refs)?; + self.write_common(out)?; + + let len_position = out.stream_position()?; + out.write_all(&[0, 0])?; + + self.rdata.write_compressed_to(out, name_refs)?; + let end = out.stream_position()?; + + out.seek(SeekFrom::Start(len_position))?; + out.write_all(&((end - len_position - 2) as u16).to_be_bytes())?; + out.seek(SeekFrom::Start(end))?; + Ok(()) + } +} + +impl Hash for ResourceRecord<'_> { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.class.hash(state); + self.rdata.hash(state); + } +} + +impl PartialEq for ResourceRecord<'_> { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.class == other.class && self.rdata == other.rdata + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + dns::rdata::NULL, + lib::{ToString, Vec}, + }; + + #[cfg(feature = "std")] + use crate::rdata::TXT; + + #[test] + fn test_parse() { + let bytes = b"\x04_srv\x04_udp\x05local\x00\x00\x01\x00\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff"; + let rr = ResourceRecord::parse(&mut BytesBuffer::new(bytes)).unwrap(); + + assert_eq!("_srv._udp.local", rr.name.to_string()); + assert_eq!(CLASS::IN, rr.class); + assert_eq!(10, rr.ttl); + assert_eq!(4, rr.rdata.len()); + assert!(!rr.cache_flush); + + match rr.rdata { + RData::A(a) => assert_eq!(4294967295, a.address), + _ => panic!("invalid rdata"), + } + } + + #[test] + fn test_empty_rdata() { + let rr = ResourceRecord { + class: CLASS::NONE, + name: "_srv._udp.local".try_into().unwrap(), + ttl: 0, + rdata: RData::Empty(TYPE::A), + cache_flush: false, + }; + + assert_eq!(rr.rdata.type_code(), TYPE::A); + assert_eq!(rr.rdata.len(), 0); + + let mut data = Vec::new(); + rr.write_to(&mut data).expect("failed to write"); + + let parsed_rr = + ResourceRecord::parse(&mut BytesBuffer::new(&data)).expect("failed to parse"); + assert_eq!(parsed_rr.rdata.type_code(), TYPE::A); + assert_eq!(parsed_rr.rdata.len(), 0); + assert!(matches!(parsed_rr.rdata, RData::Empty(TYPE::A))); + } + + #[test] + fn test_cache_flush_parse() { + let bytes = b"\x04_srv\x04_udp\x05local\x00\x00\x01\x80\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff"; + let rr = ResourceRecord::parse(&mut BytesBuffer::new(bytes)).unwrap(); + + assert_eq!(CLASS::IN, rr.class); + assert!(rr.cache_flush); + } + + #[test] + fn test_write() { + let mut out = Vec::new(); + let rdata = [255u8; 4]; + + let rr = ResourceRecord { + class: CLASS::IN, + name: "_srv._udp.local".try_into().unwrap(), + ttl: 10, + rdata: RData::NULL(0, NULL::new(&rdata).unwrap()), + cache_flush: false, + }; + + assert!(rr.write_to(&mut out).is_ok()); + assert_eq!( + b"\x04_srv\x04_udp\x05local\x00\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff", + &out[..] + ); + assert_eq!(out.len(), rr.len()); + } + + #[test] + fn test_append_to_vec_cache_flush() { + let mut out = Vec::new(); + let rdata = [255u8; 4]; + + let rr = ResourceRecord { + class: CLASS::IN, + name: "_srv._udp.local".try_into().unwrap(), + ttl: 10, + rdata: RData::NULL(0, NULL::new(&rdata).unwrap()), + cache_flush: true, + }; + + assert!(rr.write_to(&mut out).is_ok()); + assert_eq!( + b"\x04_srv\x04_udp\x05local\x00\x00\x00\x80\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff", + &out[..] + ); + assert_eq!(out.len(), rr.len()); + } + + #[test] + fn test_match_qclass() { + let rr = ResourceRecord { + class: CLASS::IN, + name: "_srv._udp.local".try_into().unwrap(), + ttl: 10, + rdata: RData::NULL(0, NULL::new(&[255u8; 4]).unwrap()), + cache_flush: false, + }; + + assert!(rr.match_qclass(QCLASS::ANY)); + assert!(rr.match_qclass(CLASS::IN.into())); + assert!(!rr.match_qclass(CLASS::CS.into())); + } + + #[test] + fn test_match_qtype() { + let rr = ResourceRecord { + class: CLASS::IN, + name: "_srv._udp.local".try_into().unwrap(), + ttl: 10, + rdata: RData::A(crate::rdata::A { address: 0 }), + cache_flush: false, + }; + + assert!(rr.match_qtype(QTYPE::ANY)); + assert!(rr.match_qtype(TYPE::A.into())); + assert!(!rr.match_qtype(TYPE::WKS.into())); + } + + #[test] + #[cfg(feature = "std")] + fn test_eq() { + let a = ResourceRecord::new( + Name::new_unchecked("_srv.local"), + CLASS::IN, + 10, + RData::TXT(TXT::new().with_string("text").unwrap()), + ); + let b = ResourceRecord::new( + Name::new_unchecked("_srv.local"), + CLASS::IN, + 10, + RData::TXT(TXT::new().with_string("text").unwrap()), + ); + + assert_eq!(a, b); + assert_eq!(get_hash(&a), get_hash(&b)); + } + + #[test] + #[cfg(feature = "std")] + fn test_hash_ignore_ttl() { + let a = ResourceRecord::new( + Name::new_unchecked("_srv.local"), + CLASS::IN, + 10, + RData::TXT(TXT::new().with_string("text").unwrap()), + ); + let mut b = ResourceRecord::new( + Name::new_unchecked("_srv.local"), + CLASS::IN, + 10, + RData::TXT(TXT::new().with_string("text").unwrap()), + ); + + assert_eq!(get_hash(&a), get_hash(&b)); + b.ttl = 50; + + assert_eq!(get_hash(&a), get_hash(&b)); + } + + #[cfg(feature = "std")] + fn get_hash(rr: &ResourceRecord) -> u64 { + let mut hasher = std::hash::DefaultHasher::default(); + rr.hash(&mut hasher); + hasher.finish() + } + + #[test] + #[cfg(feature = "std")] + fn parse_sample_files() -> Result<(), Box> { + for file_path in std::fs::read_dir("samples/zonefile")? { + let bytes = std::fs::read(file_path?.path())?; + let mut data = BytesBuffer::new(&bytes); + while data.has_remaining() { + crate::ResourceRecord::parse(&mut data)?; + } + } + + Ok(()) + } + + #[test] + #[cfg(feature = "std")] + fn write_compressed_to_should_not_seed_to_end() { + let mut q = crate::Packet::new_reply(2); + q.answers.push(ResourceRecord::new( + Name::new("a.example.com").unwrap(), + CLASS::IN, + 60, + RData::A(crate::rdata::A { address: 1 }), + )); + q.answers.push(ResourceRecord::new( + Name::new("b.example.com").unwrap(), + CLASS::IN, + 60, + RData::A(crate::rdata::A { address: 2 }), + )); + + // (A) misplaced record: pre-sized Vec cursor + let mut c = std::io::Cursor::new(vec![0u8; 300]); + q.write_compressed_to(&mut c).unwrap(); + assert_eq!(c.into_inner().len(), 300); + + // (B) panic: 512-byte slice cursor + let mut buf = [0u8; 512]; + let mut c = crate::cursor::Cursor::new(&mut buf[..]); + q.write_compressed_to(&mut c).unwrap(); + } +} diff --git a/vendor/simple-dns/src/dns/wire_format.rs b/vendor/simple-dns/src/dns/wire_format.rs new file mode 100644 index 00000000..a75c4415 --- /dev/null +++ b/vendor/simple-dns/src/dns/wire_format.rs @@ -0,0 +1,32 @@ +use crate::{ + bytes_buffer::BytesBuffer, + lib::{Seek, Write}, +}; + +/// Represents anything that can be part of a dns packet (Question, Resource Record, RData) +pub(crate) trait WireFormat<'a> { + const MINIMUM_LEN: usize; + + /// Parse the contents of the data buffer starting at the given `position` + /// It is necessary to pass the full buffer to this function, to be able to correctly implement name compression + /// The implementor must `position` to ensure that is at the end of the data just parsed + fn parse(data: &mut BytesBuffer<'a>) -> crate::Result + where + Self: Sized; + + /// Write this part bytes to the writer + fn write_to(&self, out: &mut T) -> crate::Result<()>; + + fn write_compressed_to( + &'a self, + out: &mut T, + _name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>, + ) -> crate::Result<()> { + self.write_to(out) + } + + /// Returns the length in bytes of this content + fn len(&self) -> usize { + Self::MINIMUM_LEN + } +} diff --git a/vendor/simple-dns/src/lib.rs b/vendor/simple-dns/src/lib.rs new file mode 100644 index 00000000..b822827c --- /dev/null +++ b/vendor/simple-dns/src/lib.rs @@ -0,0 +1,147 @@ +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + +mod bytes_buffer; +mod cursor; +mod dns; +mod seek; +mod simple_dns_error; +mod write; + +pub use simple_dns_error::SimpleDnsError; + +pub use dns::*; + +mod lib { + mod core { + #[cfg(not(feature = "std"))] + pub use core::*; + #[cfg(feature = "std")] + pub use std::*; + } + + pub use self::core::net::Ipv4Addr; + pub use self::core::net::Ipv6Addr; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::borrow::Cow; + #[cfg(feature = "std")] + pub use std::borrow::Cow; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::string::{FromUtf8Error, String, ToString}; + #[cfg(feature = "std")] + pub use std::string::{FromUtf8Error, String, ToString}; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::vec::Vec; + #[cfg(feature = "std")] + pub use std::vec::Vec; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::vec; + #[cfg(feature = "std")] + pub use std::vec; + + #[allow(unused)] + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::boxed::Box; + #[allow(unused)] + #[cfg(feature = "std")] + pub use std::boxed::Box; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::format; + #[cfg(feature = "std")] + pub use std::format; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::collections::BTreeMap; + #[cfg(feature = "std")] + pub use std::collections::BTreeMap; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::collections::btree_map::Entry as BTreeEntry; + + #[cfg(feature = "std")] + pub use std::collections::btree_map::Entry as BTreeEntry; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::collections::BTreeSet; + #[cfg(feature = "std")] + pub use std::collections::BTreeSet; + + pub use crate::seek::Seek; + pub use crate::seek::SeekFrom; + pub use crate::write::Write; + + #[cfg(not(feature = "std"))] + pub use crate::cursor::Cursor; + #[cfg(feature = "std")] + pub use std::io::Cursor; + + #[cfg(feature = "std")] + pub use std::collections::HashMap; + + pub use self::core::array::TryFromSliceError; + pub use self::core::error::Error; + pub use self::core::result::Result; + + pub use self::core::hash::Hash; + pub use self::core::hash::Hasher; + pub use self::core::slice::Iter; + + pub use self::core::convert::TryFrom; + pub use self::core::ops::Deref; + pub use self::core::ops::DerefMut; + #[allow(unused)] + pub use self::core::str::FromStr; + + pub mod fmt { + pub use super::core::fmt::*; + } + + pub mod str { + pub use super::core::str::*; + } + + pub mod mem { + pub use super::core::mem::*; + } +} + +/// Alias type for Result; +pub type Result = lib::Result; + +#[allow(missing_docs)] +#[doc(hidden)] +#[cfg(debug_assertions)] +pub mod testing { + use super::rdata::RR; + use crate::{lib::Vec, WireFormat}; + + #[allow(private_bounds)] + pub fn type_code() -> u16 { + T::TYPE_CODE + } + + #[allow(private_bounds)] + pub fn parse<'a, T: WireFormat<'a>>(bytes: &'a [u8]) -> T { + let mut data = crate::bytes_buffer::BytesBuffer::new(bytes); + T::parse(&mut data).expect("Failed to parse") + } + + #[allow(private_bounds)] + pub fn get_bytes<'a, T: WireFormat<'a>>(data: T) -> Vec { + let mut bytes = Vec::new(); + data.write_to(&mut bytes).expect("Failed to write to vec"); + bytes + } +} diff --git a/vendor/simple-dns/src/seek.rs b/vendor/simple-dns/src/seek.rs new file mode 100644 index 00000000..e6d5a2e4 --- /dev/null +++ b/vendor/simple-dns/src/seek.rs @@ -0,0 +1,36 @@ +pub trait Seek { + fn seek(&mut self, pos: SeekFrom) -> crate::Result; + + fn stream_position(&mut self) -> crate::Result { + self.seek(SeekFrom::Current(0)) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum SeekFrom { + Start(u64), + End(i64), + Current(i64), +} + +#[cfg(feature = "std")] +impl Seek for T +where + T: std::io::Seek, +{ + fn seek(&mut self, pos: SeekFrom) -> crate::Result { + self.seek(pos.into()) + .map_err(|_| crate::SimpleDnsError::FailedToWrite) + } +} + +#[cfg(feature = "std")] +impl std::convert::From for std::io::SeekFrom { + fn from(val: SeekFrom) -> Self { + match val { + SeekFrom::Start(pos) => std::io::SeekFrom::Start(pos), + SeekFrom::End(pos) => std::io::SeekFrom::End(pos), + SeekFrom::Current(pos) => std::io::SeekFrom::Current(pos), + } + } +} diff --git a/vendor/simple-dns/src/simple_dns_error.rs b/vendor/simple-dns/src/simple_dns_error.rs new file mode 100644 index 00000000..95eb3dcc --- /dev/null +++ b/vendor/simple-dns/src/simple_dns_error.rs @@ -0,0 +1,86 @@ +use crate::lib::fmt::{Display, Formatter, Result}; +use crate::lib::{Error, FromUtf8Error, TryFromSliceError}; + +/// Error types for SimpleDns +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum SimpleDnsError { + /// Invalid value for CLASS type + InvalidClass(u16), + /// Invalid value for QCLASS type + InvalidQClass(u16), + /// Invalid value for QTYPE type + InvalidQType(u16), + /// Service Name doesn't follow RFC rules + InvalidServiceName, + /// Service Name Label doesn't follow RFC rules + InvalidServiceLabel, + /// Character String doesn't follow RFC rules + InvalidCharacterString, + /// Provided data is not valid for a header + InvalidHeaderData, + /// Provided data is not valid for a DNS Packet + InvalidDnsPacket, + /// Attempted to perform an invalid operation + AttemptedInvalidOperation, + /// Incomplete dns packet, should try again after more data available + InsufficientData, + /// Failed to write the packet to the provided buffer + FailedToWrite, + /// Invalid utf8 string + InvalidUtf8String(FromUtf8Error), +} + +impl From for SimpleDnsError { + fn from(_: TryFromSliceError) -> Self { + Self::InvalidDnsPacket + } +} + +#[cfg(feature = "std")] +impl From for SimpleDnsError { + fn from(_value: std::io::Error) -> Self { + Self::FailedToWrite + } +} + +impl Error for SimpleDnsError {} + +impl Display for SimpleDnsError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + SimpleDnsError::InvalidClass(class) => { + write!(f, "Provided class is invalid: {class}") + } + SimpleDnsError::InvalidQClass(qclass) => { + write!(f, "Provided Qclass is invalid: {qclass}") + } + SimpleDnsError::InvalidQType(qtype) => { + write!(f, "Provided QType is invalid: {qtype}") + } + SimpleDnsError::InvalidServiceName => write!(f, "Provided service name is not valid"), + SimpleDnsError::InvalidServiceLabel => { + write!(f, "Provied service name contains invalid label") + } + SimpleDnsError::InvalidCharacterString => { + write!(f, "Provided character string is not valid") + } + SimpleDnsError::InvalidHeaderData => { + write!(f, "Provided header information is invalid") + } + SimpleDnsError::InvalidDnsPacket => { + write!(f, "Provided information is not a valid DNS packet") + } + SimpleDnsError::AttemptedInvalidOperation => { + write!(f, "Attempted to perform an invalid operation") + } + SimpleDnsError::InsufficientData => write!(f, "Incomplete dns packet"), + SimpleDnsError::FailedToWrite => { + write!(f, "Failed to write the packet to provided buffer") + } + SimpleDnsError::InvalidUtf8String(e) => { + write!(f, "Invalid utf8 string: {e}") + } + } + } +} diff --git a/vendor/simple-dns/src/write.rs b/vendor/simple-dns/src/write.rs new file mode 100644 index 00000000..7f060685 --- /dev/null +++ b/vendor/simple-dns/src/write.rs @@ -0,0 +1,77 @@ +pub trait Write { + fn write(&mut self, buf: &[u8]) -> crate::Result; + + fn flush(&mut self) -> crate::Result<()>; + + fn write_all(&mut self, mut buf: &[u8]) -> crate::Result<()> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => panic!("write() returned Ok(0)"), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } +} + +#[cfg(not(feature = "std"))] +impl Write for &mut T { + #[inline] + fn write(&mut self, buf: &[u8]) -> crate::Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> crate::Result<()> { + T::flush(self) + } +} + +#[cfg(feature = "std")] +impl Write for T +where + T: std::io::Write, +{ + fn write(&mut self, buf: &[u8]) -> crate::Result { + self.write(buf) + .map_err(|_| crate::SimpleDnsError::FailedToWrite) + } + + fn flush(&mut self) -> crate::Result<()> { + self.flush() + .map_err(|_| crate::SimpleDnsError::FailedToWrite) + } +} + +#[cfg(all(feature = "alloc", not(feature = "std")))] +impl Write for crate::lib::Vec { + fn write(&mut self, buf: &[u8]) -> crate::Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> crate::Result<()> { + Ok(()) + } +} + +#[cfg(not(feature = "std"))] +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, buf: &[u8]) -> crate::Result { + let amt = core::cmp::min(buf.len(), self.len()); + if !buf.is_empty() && amt == 0 { + return Err(crate::SimpleDnsError::FailedToWrite); + } + let (a, b) = core::mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn flush(&mut self) -> crate::Result<()> { + Ok(()) + } +} diff --git a/vendor/simple-dns/tests/packet_tests.rs b/vendor/simple-dns/tests/packet_tests.rs new file mode 100644 index 00000000..54284a71 --- /dev/null +++ b/vendor/simple-dns/tests/packet_tests.rs @@ -0,0 +1,181 @@ +use simple_dns::{ + rdata::{RData, A}, + Name, Packet, ResourceRecord, SimpleDnsError, CLASS, QCLASS, QTYPE, RCODE, TYPE, +}; + +#[test] +fn parse_ptr_with_compression() { + let data: &[u8] = &[ + 0x00, 0x00, 0x84, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x07, 0x5f, 0x64, 0x6e, 0x73, 0x2d, 0x73, 0x64, + 0x04, 0x5f, 0x75, 0x64, 0x70, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x80, + 0x01, 0xc0, 0x0c, 0x00, 0x0c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x11, 0x09, 0x5f, + 0x73, 0x66, 0x74, 0x70, 0x2d, 0x73, 0x73, 0x68, 0x04, 0x5f, 0x74, 0x63, 0x70, 0xc0, 0x23, + ]; + + assert!(Packet::parse(data).is_ok()); +} + +#[test] +fn parse_cache_flush_package() { + let data: &[u8] = &[ + 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x01, + 0x63, 0x01, 0x63, 0x01, 0x64, 0x01, 0x33, 0x01, 0x36, 0x01, 0x31, 0x01, 0x37, 0x01, 0x65, + 0x01, 0x62, 0x01, 0x38, 0x01, 0x37, 0x01, 0x39, 0x01, 0x38, 0x01, 0x32, 0x01, 0x31, 0x01, + 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, + 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01, 0x38, 0x01, 0x65, 0x01, + 0x66, 0x03, 0x69, 0x70, 0x36, 0x04, 0x61, 0x72, 0x70, 0x61, 0x00, 0x00, 0x0C, 0x80, 0x01, + 0x00, 0x00, 0x00, 0x78, 0x00, 0x13, 0x0B, 0x69, 0x6C, 0x73, 0x6F, 0x6E, 0x2D, 0x75, 0x78, + 0x33, 0x31, 0x65, 0x05, 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x00, 0xC0, 0x60, 0x00, 0x01, 0x80, + 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x48, 0x02, 0x37, 0x32, 0x01, + 0x31, 0x03, 0x31, 0x36, 0x38, 0x03, 0x31, 0x39, 0x32, 0x07, 0x69, 0x6E, 0x2D, 0x61, 0x64, + 0x64, 0x72, 0xC0, 0x50, 0x00, 0x0C, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x02, 0xC0, + 0x60, 0xC0, 0x60, 0x00, 0x1C, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x10, 0xFE, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x89, 0x78, 0xBE, 0x71, 0x63, 0xDC, 0xC3, + ]; + + assert!(Packet::parse(data).is_ok()); +} + +#[test] +fn query_google_com() -> Result<(), SimpleDnsError> { + let bytes = b"\x00\x03\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"; + let packet = Packet::parse(bytes)?; + + assert!(!packet.has_flags(simple_dns::PacketFlag::RESPONSE)); + assert_eq!(1, packet.questions.len()); + assert_eq!("google.com", packet.questions[0].qname.to_string()); + assert_eq!(QTYPE::TYPE(TYPE::A), packet.questions[0].qtype); + assert_eq!(QCLASS::CLASS(CLASS::IN), packet.questions[0].qclass); + + Ok(()) +} + +#[test] +fn reply_google_com() -> Result<(), SimpleDnsError> { + let bytes = b"\x00\x03\x81\x80\x00\x01\x00\x0b\x00\x00\x00\x00\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\ + \x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x23\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\ + \x00\x04\x4a\x7d\xec\x25\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x27\xc0\x0c\x00\x01\x00\x01\x00\x00\ + \x00\x04\x00\x04\x4a\x7d\xec\x20\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x28\xc0\x0c\x00\x01\x00\x01\ + \x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x21\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x29\xc0\x0c\x00\x01\ + \x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x22\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x24\xc0\x0c\ + \x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x2e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x4a\x7d\xec\x26"; + + let packet = Packet::parse(bytes)?; + + assert!(packet.has_flags(simple_dns::PacketFlag::RESPONSE)); + assert_eq!(1, packet.questions.len()); + assert_eq!(11, packet.answers.len()); + + assert_eq!("google.com", packet.answers[0].name.to_string()); + assert_eq!(CLASS::IN, packet.answers[0].class); + assert_eq!(4, packet.answers[0].ttl); + + match &packet.answers[0].rdata { + RData::A(a) => { + assert_eq!(1249766435, a.address) + } + _ => panic!("invalid RDATA"), + } + + Ok(()) +} + +#[test] +fn compression_multiple_names() { + let mut packet = Packet::new_query(0); + + packet.answers.push(ResourceRecord::new( + Name::new_unchecked("a._tcp.local"), + CLASS::IN, + 10, + RData::A(A { address: 10 }), + )); + packet.answers.push(ResourceRecord::new( + Name::new_unchecked("b._tcp.local"), + CLASS::IN, + 10, + RData::A(A { address: 10 }), + )); + + packet.answers.push(ResourceRecord::new( + Name::new_unchecked("b._tcp.local"), + CLASS::IN, + 10, + RData::A(A { address: 10 }), + )); + + let buffer = packet + .build_bytes_vec_compressed() + .expect("Failed to generate packet"); + + assert!(Packet::parse(&buffer[..]).is_ok()); +} + +#[test] +fn parse_edns_packet() { + let mut packet = Packet::new_reply(0); + *packet.rcode_mut() = RCODE::BADVERS; + *packet.opt_mut() = Some(simple_dns::rdata::OPT { + opt_codes: Default::default(), + udp_packet_size: 500, + version: 3, + }); + + let buffer = packet.build_bytes_vec().expect("Failed to write packet"); + let packet = Packet::parse(&buffer[..]).expect("Failed to parse packet"); + + assert_eq!(500, packet.opt().map(|opt| opt.udp_packet_size).unwrap()); + assert_eq!(RCODE::BADVERS, packet.rcode()); + assert_eq!(3, packet.opt().map(|opt| opt.version).unwrap()); +} + +#[test] +fn compressed_rdata_has_correct_length() { + let bytes = [ + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x5F, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x07, 0x5F, 0x64, 0x6E, 0x73, 0x2D, 0x73, 0x64, + 0x04, 0x5F, 0x75, 0x64, 0x70, 0x05, 0x6C, 0x6F, 0x63, 0x61, 0x6C, 0x00, 0x00, 0x0C, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x0F, 0x07, 0x5F, 0x6D, 0x79, 0x5F, 0x62, 0x67, 0x73, + 0x04, 0x5F, 0x74, 0x63, 0x70, 0xC0, 0x23, + ]; + + assert!(Packet::parse(&bytes[..]).is_ok()); +} + +#[test] +fn build_bytes_vec_after_parsing_compressed_have_correct_length() { + let name = "foobar"; + + let mut original = Packet::new_reply(0); + original.answers.push(simple_dns::ResourceRecord::new( + simple_dns::Name::new("a").unwrap(), + simple_dns::CLASS::IN, + 30, + simple_dns::rdata::RData::CNAME(simple_dns::Name::new(name).unwrap().into()), + )); + original.answers.push(simple_dns::ResourceRecord::new( + simple_dns::Name::new("a").unwrap(), + simple_dns::CLASS::IN, + 30, + simple_dns::rdata::RData::CNAME(simple_dns::Name::new(name).unwrap().into()), + )); + + let compressed = original.build_bytes_vec_compressed().unwrap(); + let decompressed = Packet::parse(&compressed).unwrap(); + + let encoded = decompressed.build_bytes_vec().unwrap(); + + assert_eq!(encoded, original.build_bytes_vec().unwrap()); + // Error: mistakenly set the first 2 bits as if it is a pointer ------+ + // | + // encoded: [ ...header, ...1st answer, 2nd: qname, type, class, ttl, __2__, 6, "foobar", 0] + // expected: [ ...header, ...1st answer, 2nd: qname, type, class, ttl, __8__, 6, "foobar", 0] + + let parsed = Packet::parse(&encoded).unwrap(); // Err InsufficientData + + assert_eq!( + parsed.build_bytes_vec().unwrap(), + original.build_bytes_vec().unwrap() + ); +}