fix(mobile): resolve relays behind DNS that sets an EDNS Z flag (#1092)

simple-dns 0.12.0 reads the EDNS OPT record's TTL at the wrong offsets: it
takes the extended RCODE from the low byte, where RFC 6891 puts the DO/Z
flags. A resolver that sets a Z bit -- a Clash-style fake-IP DNS on the
router does -- turns every NOERROR answer into RCODE 16, so iroh resolves
nothing. The gateway never reaches a relay, pairing codes carry none, and
a phone off the LAN cannot connect ("It didn't answer").

Vendor simple-dns 0.12.0 with the offsets fixed and patch it in for both
the desktop and the mobile workspace. tests/edns_z_flag.rs drives iroh's
resolver against a nameserver that sets the flag, from the root package so
CI's default `cargo test` runs it and fails if the patch stops applying.
This commit is contained in:
l0ng-ai
2026-10-04 16:10:36 +08:00
committed by GitHub
parent 54978409c2
commit e60bf9f12c
90 changed files with 8898 additions and 4 deletions
Generated
+2 -2
View File
@@ -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",
+9
View File
@@ -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"
-2
View File
@@ -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",
]
+5
View File
@@ -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" }
+70
View File
@@ -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<Vec<std::net::IpAddr>, 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()])
);
}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "5c7ec98798d7c9f7d76df7aa3bbe754913ff5159"
},
"path_in_vcs": "simple-dns"
}
+220
View File
@@ -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
+16
View File
@@ -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",
]
+54
View File
@@ -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 <ilson.balliego@gmail.com>"]
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"
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "simple-dns"
version = "0.12.0"
authors = ["Ilson Roberto Balliego Junior <ilson.balliego@gmail.com>"]
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
+21
View File
@@ -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.
+109
View File
@@ -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<u8>
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+325
View File
@@ -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<Self> {
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> {
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<u32> {
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<u16> {
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<u128> {
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<i32> {
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<u32> {
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<u16> {
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<u8> {
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<const N: usize>(&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<const N: usize>(&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());
}
}
+157
View File
@@ -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<T> {
inner: T,
pos: u64,
}
impl<T> Cursor<T> {
pub const fn new(inner: T) -> Cursor<T> {
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<T> Clone for Cursor<T>
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<T> Seek for Cursor<T>
where
T: AsRef<[u8]>,
{
fn seek(&mut self, style: SeekFrom) -> crate::Result<u64> {
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<u64> {
Ok(self.pos)
}
}
impl Write for Cursor<&mut [u8]> {
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
slice_write(&mut self.pos, self.inner, buf)
}
fn flush(&mut self) -> crate::Result<()> {
Ok(())
}
}
impl<const N: usize> Write for Cursor<[u8; N]> {
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
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<Vec<u8>> {
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
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<u8>> {
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
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<usize> {
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<u8>, 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()
}
+161
View File
@@ -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> {
Self::internal_new(Cow::Borrowed(data))
}
fn internal_new(data: Cow<'a, [u8]>) -> crate::Result<Self> {
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<CharacterString<'a>> for String {
type Error = crate::SimpleDnsError;
fn try_from(val: CharacterString<'a>) -> Result<Self, Self::Error> {
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<Self>
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<T: Write>(&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<Self, Self::Error> {
CharacterString::internal_new(Cow::Borrowed(value.as_bytes()))
}
}
impl TryFrom<String> for CharacterString<'_> {
type Error = crate::SimpleDnsError;
fn try_from(value: String) -> Result<Self, Self::Error> {
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}");
}
}
+233
View File
@@ -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<OPT<'a>>,
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<Self> {
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<T: Write>(
&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<ResourceRecord<'a>> {
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<ResourceRecord<'a>>) {
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));
}
}
+292
View File
@@ -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<u16> {
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<u16> {
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<u16> {
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<u16> {
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<u16> {
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<bool> {
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<RCODE> {
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<OPCODE> {
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]);
}
}
+286
View File
@@ -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<TYPE> for QTYPE {
fn from(v: TYPE) -> Self {
Self::TYPE(v)
}
}
impl TryFrom<u16> for QTYPE {
type Error = crate::SimpleDnsError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
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<QTYPE> 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<u16> for CLASS {
type Error = crate::SimpleDnsError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
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<CLASS> for QCLASS {
fn from(v: CLASS) -> Self {
Self::CLASS(v)
}
}
impl TryFrom<u16> for QCLASS {
type Error = crate::SimpleDnsError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
255 => Ok(QCLASS::ANY),
v => CLASS::try_from(v).map(|x| x.into()),
}
}
}
impl From<QCLASS> 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<u16> 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<u16> 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,
}
}
}
+797
View File
@@ -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<Label<'a>>,
}
impl<'a> Name<'a> {
/// Creates a new Name. Returns [`Result::<Name>::Ok`] if given `name` contents are valid.
pub fn new(name: &'a str) -> crate::Result<Self> {
let labels = LabelsIter::new(name.as_bytes())
.map(Label::new)
.collect::<Result<Vec<Label>, _>>()?;
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<Name<'_>> {
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<T: Write>(&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<T: Write + Seek>(
&'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<Item = &[u8]> {
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<Self>
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<Label<'a>>,
) -> crate::Result<Option<usize>> {
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<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.plain_append(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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::<usize>()
+ Self::MINIMUM_LEN
}
}
impl<'a> TryFrom<&'a str> for Name<'a> {
type Error = crate::SimpleDnsError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
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<H: Hasher>(&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<Self::Item> {
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<T: Into<Cow<'a, [u8]>>>(data: T) -> crate::Result<Self> {
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<T: Into<Cow<'a, [u8]>>>(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(())
}
}
+278
View File
@@ -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<Question<'a>>,
/// Answers section
pub answers: Vec<ResourceRecord<'a>>,
/// Name servers section
pub name_servers: Vec<ResourceRecord<'a>>,
/// Aditional records section.
/// DO NOT use this field to add OPT record, use [`Packet::opt_mut`] instead
pub additional_records: Vec<ResourceRecord<'a>>,
}
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<OPT<'a>> {
&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<Self> {
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<ResourceRecord> =
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<T: WireFormat<'a>>(
data: &mut BytesBuffer<'a>,
items_count: u16,
) -> crate::Result<Vec<T>> {
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`<u8>`](`Vec<T>`) and write the contents of this package in wire format
///
/// This call will allocate a `Vec<u8>` of 900 bytes, which is enough for a jumbo UDP packet
pub fn build_bytes_vec(&self) -> crate::Result<Vec<u8>> {
let mut out = Vec::with_capacity(900);
self.write_to(&mut out)?;
Ok(out)
}
/// Creates a new [Vec`<u8>`](`Vec<T>`) and write the contents of this package in wire format
/// with compression enabled
///
/// This call will allocate a `Vec<u8>` of 900 bytes, which is enough for a jumbo UDP packet
pub fn build_bytes_vec_compressed(&self) -> crate::Result<Vec<u8>> {
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<T: Write>(&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<T: Write + Seek>(&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<T: Write>(&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());
}
}
+142
View File
@@ -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<T: Write>(&self, out: &mut T) -> crate::Result<()> {
let qclass: u16 = match self.unicast_response {
true => Into::<u16>::into(self.qclass) | 0x8000,
false => self.qclass.into(),
};
out.write_all(&Into::<u16>::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<Self> {
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<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.qname.write_to(out)?;
self.write_common(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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);
}
}
+88
View File
@@ -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<Self>
where
Self: Sized,
{
data.get_u32().map(|address| Self { address })
}
fn write_to<T: Write>(&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<Ipv4Addr> 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<dyn std::error::Error>> {
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::<Ipv4Addr>()?.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(())
}
}
+89
View File
@@ -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<Self>
where
Self: Sized,
{
data.get_u128().map(|address| Self { address })
}
fn write_to<T: Write>(&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<Ipv6Addr> 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<dyn std::error::Error>> {
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::<Ipv6Addr>()?.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(())
}
}
+104
View File
@@ -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<Self>
where
Self: Sized,
{
let subtype = data.get_u16()?;
let hostname = Name::parse(data)?;
Ok(Self { subtype, hostname })
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
out.write_all(&self.subtype.to_be_bytes())?;
self.hostname.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+134
View File
@@ -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<Self>
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<T: Write>(&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[..]);
}
}
+113
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+102
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+115
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+114
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+145
View File
@@ -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<Self>
where
Self: Sized,
{
let address = data.get_array()?;
Ok(Self { address })
}
fn write_to<T: Write>(&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<Self>
where
Self: Sized,
{
let address = data.get_array()?;
Ok(Self { address })
}
fn write_to<T: Write>(&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<EUI48> for [u8; 6] {
fn from(value: EUI48) -> Self {
value.address
}
}
impl From<EUI64> 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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())
}
}
+105
View File
@@ -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<Self>
where
Self: Sized,
{
let cpu = CharacterString::parse(data)?;
let os = CharacterString::parse(data)?;
Ok(Self { cpu, os })
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.cpu.write_to(out)?;
self.os.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+171
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+102
View File
@@ -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<Self>
where
Self: Sized,
{
let address = CharacterString::parse(data)?;
let sa = CharacterString::parse(data)?;
Ok(Self { address, sa })
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.address.write_to(out)?;
self.sa.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+91
View File
@@ -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<Self>
where
Self: Sized,
{
let preference = data.get_u16()?;
let exchanger = Name::parse(data)?;
Ok(Self {
preference,
exchanger,
})
}
fn write_to<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+138
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+231
View File
@@ -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<Self>
where
Self: Sized,
{
$w::parse(data).map(|n| $t(n))
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.0.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<Self>
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<T: Write>(
&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<T: Write + Seek>(
&'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<RData<'a>> {
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<TYPE> 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<u16> 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;
+87
View File
@@ -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<Self>
where
Self: Sized,
{
let rmailbox = Name::parse(data)?;
let emailbox = Name::parse(data)?;
Ok(Self { rmailbox, emailbox })
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.rmailbox.write_to(out)?;
self.emailbox.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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());
}
}
+243
View File
@@ -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
*/
+108
View File
@@ -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<Self>
where
Self: Sized,
{
let preference = data.get_u16()?;
let exchange = Name::parse(data)?;
Ok(Self {
preference,
exchange,
})
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
out.write_all(&self.preference.to_be_bytes())?;
self.exchange.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+120
View File
@@ -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<Self>
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<T: Write>(&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());
}
}
+154
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+187
View File
@@ -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<NsecTypeBitMap<'a>>,
}
/// 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<Self>
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<T: Write>(&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::<usize>()
}
}
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<dyn std::error::Error>> {
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(())
}
}
+64
View File
@@ -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<Self> {
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<Self>
where
Self: Sized,
{
Self::new(data.get_remaining())
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
out.write_all(&self.data)
}
fn len(&self) -> usize {
self.length as usize
}
}
+224
View File
@@ -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<OPTCode<'a>>,
/// 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<Self>
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<T: Write>(&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);
}
}
+111
View File
@@ -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<Self>
where
Self: Sized,
{
let preference = data.get_u16()?;
let intermediate_host = Name::parse(data)?;
Ok(Self {
preference,
intermediate_host,
})
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
out.write_all(&self.preference.to_be_bytes())?;
self.intermediate_host.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+103
View File
@@ -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<Self>
where
Self: Sized,
{
let mbox = Name::parse(data)?;
let txt = Name::parse(data)?;
Ok(RP { mbox, txt })
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.mbox.write_to(out)?;
self.txt.write_to(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+152
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+161
View File
@@ -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<T: Write>(&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<Self>
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<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.mname.write_to(out)?;
self.rname.write_to(out)?;
self.write_common(out)
}
fn write_compressed_to<T: Write + Seek>(
&'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<dyn std::error::Error>> {
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(())
}
}
+137
View File
@@ -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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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(())
}
}
+481
View File
@@ -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<u16, SVCParam<'a>>,
}
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<Item = u16>) {
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<Item = &SVCParam<'a>> {
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<Self>
where
Self: Sized,
{
let priority = data.get_u16()?;
let target = Name::parse(data)?;
let mut params = BTreeMap::new();
let mut previous_key: Option<u16> = 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<T: Write>(&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::<usize>() + 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<u16>),
/// Additional supported protocols. Key Code 1.
Alpn(Vec<CharacterString<'a>>),
/// 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<u32>),
/// Encrypted ClientHello (ECH) configuration. Key Code 5.
Ech(Cow<'a, [u8]>),
/// IPv6 address hints. Key Code 6.
Ipv6Hint(Vec<u128>),
/// 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<Self>
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<T: Write>(&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<dyn std::error::Error>> {
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.
// <https://www.rfc-editor.org/rfc/rfc9460.html#name-test-vectors>
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}");
}
}
}
+342
View File
@@ -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<CharacterString<'a>>,
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> {
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<Item = (&[u8], Option<&[u8]>)> {
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<String, Option<String>> {
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<crate::lib::HashMap<String, Option<String>>> {
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::<Vec<&str>>();
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<crate::lib::HashMap<String, Option<String>>> for TXT<'_> {
type Error = crate::SimpleDnsError;
fn try_from(value: crate::lib::HashMap<String, Option<String>>) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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<TXT<'a>> for String {
type Error = FromUtf8Error;
fn try_from(val: TXT<'a>) -> Result<Self, Self::Error> {
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<Self>
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<T: Write>(&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<dyn Error>> {
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<dyn Error>> {
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<_>>(),
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<dyn Error>> {
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<dyn std::error::Error>> {
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<dyn Error>> {
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<dyn Error>> {
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(())
}
}
+89
View File
@@ -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<Self>
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<T: Write>(&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<dyn crate::lib::Error>> {
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::<Ipv4Addr>()?.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(())
}
}
+120
View File
@@ -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<Self>
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<T: Write>(&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<dyn crate::lib::Error>> {
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(())
}
}
+409
View File
@@ -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<T: Write>(&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<Self>
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<T: Write>(&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<T: Write + Seek>(
&'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<H: Hasher>(&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<dyn std::error::Error>> {
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();
}
}
+32
View File
@@ -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<Self>
where
Self: Sized;
/// Write this part bytes to the writer
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()>;
fn write_compressed_to<T: Write + Seek>(
&'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
}
}
+147
View File
@@ -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<T, SimpleDnsError>;
pub type Result<T> = lib::Result<T, SimpleDnsError>;
#[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<T: RR>() -> 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<u8> {
let mut bytes = Vec::new();
data.write_to(&mut bytes).expect("Failed to write to vec");
bytes
}
}
+36
View File
@@ -0,0 +1,36 @@
pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> crate::Result<u64>;
fn stream_position(&mut self) -> crate::Result<u64> {
self.seek(SeekFrom::Current(0))
}
}
#[derive(Debug, Clone, Copy)]
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}
#[cfg(feature = "std")]
impl<T> Seek for T
where
T: std::io::Seek,
{
fn seek(&mut self, pos: SeekFrom) -> crate::Result<u64> {
self.seek(pos.into())
.map_err(|_| crate::SimpleDnsError::FailedToWrite)
}
}
#[cfg(feature = "std")]
impl std::convert::From<SeekFrom> 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),
}
}
}
+86
View File
@@ -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<TryFromSliceError> for SimpleDnsError {
fn from(_: TryFromSliceError) -> Self {
Self::InvalidDnsPacket
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> 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}")
}
}
}
}
+77
View File
@@ -0,0 +1,77 @@
pub trait Write {
fn write(&mut self, buf: &[u8]) -> crate::Result<usize>;
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<T: ?Sized + Write> Write for &mut T {
#[inline]
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
T::write(self, buf)
}
#[inline]
fn flush(&mut self) -> crate::Result<()> {
T::flush(self)
}
}
#[cfg(feature = "std")]
impl<T> Write for T
where
T: std::io::Write,
{
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
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<u8> {
fn write(&mut self, buf: &[u8]) -> crate::Result<usize> {
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<usize> {
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(())
}
}
+181
View File
@@ -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()
);
}