Skip to content

Commit

Permalink
Merge branch 'main' into initial-nsec3-generation
Browse files Browse the repository at this point in the history
  • Loading branch information
ximon18 committed Nov 8, 2024
2 parents 109370d + a23a5fd commit e1c1db8
Show file tree
Hide file tree
Showing 13 changed files with 976 additions and 41 deletions.
52 changes: 51 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
run: cargo fmt --all -- --check
- run: cargo check --no-default-features --all-targets
- run: cargo test --all-features

minimal-versions:
name: Check minimal versions
runs-on: ubuntu-latest
Expand All @@ -60,4 +61,53 @@ jobs:
run: |
cargo +nightly update -Z minimal-versions
cargo check --all-features --all-targets --locked
# Note: This deliberately doesn't try to run the examples as some of them
# don't terminate and/or attempt to make network connections that will fail
# or perhaps be a nuisance to real name servers if run regularly by CI.
#
# Note: This is just a band aid, ideally there would be a way for Cargo to
# build/run examples using the feature set specified in Cargo.toml itself,
# but that isn't currently possible (see [1]).
#
# [1]: https://github.com/rust-lang/cargo/issues/4663)
build-examples:
name: Build examples
runs-on: ubuntu-latest
strategy:
matrix:
rust: [1.78.0, stable, beta, nightly]
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Rust
uses: hecrj/setup-rust-action@v2
with:
rust-version: ${{ matrix.rust }}
- name: Compile all examples
shell: bash
# TODO: Emit matrix elements based on inspecting Cargo.toml so that
# each example runs as its own GitHub Actions step/job?
run: |
# Generate the set of cargo check --example X --features Y commands to
# run and store them in a temporary script. This command works by
# extracting the example blocks from the Cargo.toml file and the set
# of features that the block indicates are needed to run the example.
# E.g. given this block in Cargo.toml:
#
# [[example]]
# name = "lookup"
# required-features = ["resolv"]
#
# It outputs a line that looks like this:
#
# cargo check --example lookup --features resolv
#
# One line per example is output and the set of lines directed to a temporary
# shell script file which is then run in the next step.
cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].targets[] | select(.kind[] | contains("example")) | "cargo check --example \(.name) --features \(."required-features" | flatten | join(","))"' > ${{ runner.temp }}/check-examples.sh
# Run the temporary script:
bash ${{ runner.temp }}/check-examples.sh
9 changes: 7 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ New
rather than a tree. ([#396])
* Changed `fmt::Display` for `HINFO` records to a show a quoted string.
([#421])
* Added support for `NAPTR` record type. ([#427] by [@weilence])

Bug fixes

* NSEC records should include themselves in the generated bitmap. ([#417])

Unstable features

* `unstable-server-transport`
Expand All @@ -31,8 +34,10 @@ Other changes

[#353]: https://github.com/NLnetLabs/domain/pull/353
[#396]: https://github.com/NLnetLabs/domain/pull/396
[#421]: https://github.com/NLnetLabs/domain/pull/412

[#417]: https://github.com/NLnetLabs/domain/pull/417
[#421]: https://github.com/NLnetLabs/domain/pull/421
[#427]: https://github.com/NLnetLabs/domain/pull/427
[@weilence]: https://github.com/weilence

## 0.10.3

Expand Down
174 changes: 174 additions & 0 deletions src/base/dig_printer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use core::fmt;

use crate::rdata::AllRecordData;

use super::zonefile_fmt::ZonefileFmt;
use super::ParsedRecord;
use super::{opt::AllOptData, Message, Rtype};

/// Interal type for printing a message in dig style
///
/// This is only exposed to users of this library as `impl fmt::Display`.
pub(super) struct DigPrinter<'a, Octs> {
pub msg: &'a Message<Octs>,
}

impl<'a, Octs: AsRef<[u8]>> fmt::Display for DigPrinter<'a, Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = self.msg.for_slice_ref();

// Header
let header = msg.header();
let counts = msg.header_counts();

writeln!(
f,
";; ->>HEADER<<- opcode: {}, rcode: {}, id: {}",
header.opcode().display_zonefile(false),
header.rcode(),
header.id()
)?;
write!(f, ";; flags: {}", header.flags())?;
writeln!(
f,
"; QUERY: {}, ANSWER: {}, AUTHORITY: {}, ADDITIONAL: {}",
counts.qdcount(),
counts.ancount(),
counts.nscount(),
counts.arcount()
)?;

// We need this later
let opt = msg.opt();

if let Some(opt) = opt.as_ref() {
writeln!(f, "\n;; OPT PSEUDOSECTION:")?;
writeln!(
f,
"; EDNS: version {}; flags: {}; udp: {}",
opt.version(),
opt.dnssec_ok(),
opt.udp_payload_size()
)?;
for option in opt.opt().iter::<AllOptData<_, _>>() {
use AllOptData::*;

match option {
Ok(opt) => match opt {
Nsid(nsid) => writeln!(f, "; NSID: {}", nsid)?,
Dau(dau) => writeln!(f, "; DAU: {}", dau)?,
Dhu(dhu) => writeln!(f, "; DHU: {}", dhu)?,
N3u(n3u) => writeln!(f, "; N3U: {}", n3u)?,
Expire(expire) => {
writeln!(f, "; EXPIRE: {}", expire)?
}
TcpKeepalive(opt) => {
writeln!(f, "; TCPKEEPALIVE: {}", opt)?
}
Padding(padding) => {
writeln!(f, "; PADDING: {}", padding)?
}
ClientSubnet(opt) => {
writeln!(f, "; CLIENTSUBNET: {}", opt)?
}
Cookie(cookie) => {
writeln!(f, "; COOKIE: {}", cookie)?
}
Chain(chain) => writeln!(f, "; CHAIN: {}", chain)?,
KeyTag(keytag) => {
writeln!(f, "; KEYTAG: {}", keytag)?
}
ExtendedError(extendederror) => {
writeln!(f, "; EDE: {}", extendederror)?
}
Other(other) => {
writeln!(f, "; {}", other.code())?;
}
},
Err(err) => {
writeln!(f, "; ERROR: bad option: {}.", err)?;
}
}
}
}

// Question
let questions = msg.question();
if counts.qdcount() > 0 {
writeln!(f, ";; QUESTION SECTION:")?;
for item in questions {
if let Ok(item) = item {
writeln!(f, "; {}", item)?;
} else {
writeln!(f, "; <invalid message>")?;
return Ok(());
};
}
}

// Answer
let section = questions.answer().unwrap();
if counts.ancount() > 0 {
writeln!(f, "\n;; ANSWER SECTION:")?;
for item in section {
if let Ok(item) = item {
write_record_item(f, &item)?;
} else {
writeln!(f, "; <invalid message>")?;
return Ok(());
};
}
}

// Authority
let section = section.next_section().unwrap().unwrap();
if counts.nscount() > 0 {
writeln!(f, "\n;; AUTHORITY SECTION:")?;
for item in section {
if let Ok(item) = item {
write_record_item(f, &item)?;
} else {
writeln!(f, "; <invalid message>")?;
return Ok(());
};
}
}

// Additional
let section = section.next_section().unwrap().unwrap();
if counts.arcount() > 1 || (opt.is_none() && counts.arcount() > 0) {
writeln!(f, "\n;; ADDITIONAL SECTION:")?;
for item in section {
if let Ok(item) = item {
if item.rtype() != Rtype::OPT {
write_record_item(f, &item)?;
}
} else {
writeln!(f, "; <invalid message>")?;
return Ok(());
};
}
}

Ok(())
}
}

fn write_record_item(
f: &mut impl fmt::Write,
item: &ParsedRecord<&[u8]>,
) -> Result<(), fmt::Error> {
let parsed = item.to_any_record::<AllRecordData<_, _>>();

match parsed {
Ok(item) => writeln!(f, "{}", item.display_zonefile(false)),
Err(_) => writeln!(
f,
"; {} {} {} {} <invalid data>",
item.owner(),
item.ttl().as_secs(),
item.class(),
item.rtype()
),
}
}
15 changes: 15 additions & 0 deletions src/base/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//!
//! [`Message`]: struct.Message.html

use super::dig_printer::DigPrinter;
use super::header::{Header, HeaderCounts, HeaderSection};
use super::iana::{Class, OptRcode, Rcode, Rtype};
use super::message_builder::{AdditionalBuilder, AnswerBuilder, PushError};
Expand Down Expand Up @@ -665,6 +666,20 @@ impl<Octs: Octets + ?Sized> Message<Octs> {
}
}

/// # Printing
impl<Octs: AsRef<[u8]>> Message<Octs> {
/// Create a wrapper that displays the message in a dig style
///
/// The dig style resembles a zonefile format (see also [`ZonefileFmt`]),
/// with additional lines that are commented out that contain information
/// about the header, OPT record and more.
///
/// [`ZonefileFmt`]: super::zonefile_fmt::ZonefileFmt
pub fn display_dig_style(&self) -> impl core::fmt::Display + '_ {
DigPrinter { msg: self }
}
}

//--- AsRef

// Octs here can’t be ?Sized or it’ll conflict with AsRef<[u8]> below.
Expand Down
1 change: 1 addition & 0 deletions src/base/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pub use self::serial::Serial;

pub mod charstr;
pub mod cmp;
mod dig_printer;
pub mod header;
pub mod iana;
pub mod message;
Expand Down
24 changes: 24 additions & 0 deletions src/base/zonefile_fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ impl<T: ZonefileFmt + ?Sized> fmt::Display for ZoneFileDisplay<'_, T> {

/// Show a value as zonefile format
pub trait ZonefileFmt {
/// Format the item as zonefile fmt into a [`fmt::Formatter`]
///
/// This method is meant for use in a `fmt::Display` implementation.
fn fmt(&self, p: &mut impl Formatter) -> Result;

/// Display the item as a zonefile
///
/// The returned object will be displayed as zonefile when printed or
/// written using `fmt::Display`.
fn display_zonefile(&self, pretty: bool) -> ZoneFileDisplay<'_, Self> {
ZoneFileDisplay {
inner: self,
Expand Down Expand Up @@ -347,4 +354,21 @@ mod test {
record.display_zonefile(false).to_string()
);
}

#[test]
fn naptr_record() {
use crate::rdata::Naptr;
let record = create_record(Naptr::<Vec<u8>, &Name<[u8]>>::new(
100,
50,
"a".parse().unwrap(),
"z3950+N2L+N2C".parse().unwrap(),
r#"!^urn:cid:.+@([^\\.]+\\.)(.*)$!\\2!i"#.parse().unwrap(),
Name::from_slice(b"\x09cidserver\x07example\x03com\x00").unwrap(),
));
assert_eq!(
r#"example.com. 3600 IN NAPTR 100 50 "a" "z3950+N2L+N2C" "!^urn:cid:.+@([^\\.]+\\.)(.*)$!\\2!i" cidserver.example.com."#,
record.display_zonefile(false).to_string()
);
}
}
17 changes: 8 additions & 9 deletions src/net/server/single_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ use super::message::Request;
use super::service::ServiceError;
use crate::base::message_builder::AdditionalBuilder;
use crate::base::opt::{AllOptData, ComposeOptData, LongOptData, OptRecord};
use crate::base::{
Message, MessageBuilder, Rtype, StreamTarget, UnknownRecordData,
};
use crate::base::{Message, MessageBuilder, ParsedName, Rtype, StreamTarget};
use crate::dep::octseq::Octets;
use crate::rdata::AllRecordData;
use std::boxed::Box;
use std::future::Future;
use std::pin::Pin;
Expand Down Expand Up @@ -145,8 +144,8 @@ impl ComposeReply for ReplyMessage {
for rr in &mut source {
let rr = rr?;
let rr = rr
.into_record::<UnknownRecordData<_>>()?
.expect("UnknownRecordData should not fail");
.into_record::<AllRecordData<_, ParsedName<_>>>()?
.expect("AllRecordData should not fail");
target.push(rr).expect("push should not fail");
}

Expand All @@ -157,8 +156,8 @@ impl ComposeReply for ReplyMessage {
for rr in &mut source {
let rr = rr?;
let rr = rr
.into_record::<UnknownRecordData<_>>()?
.expect("UnknownRecordData should not fail");
.into_record::<AllRecordData<_, ParsedName<_>>>()?
.expect("AllRecordData should not fail");
target.push(rr).expect("push should not fail");
}

Expand All @@ -171,8 +170,8 @@ impl ComposeReply for ReplyMessage {
if rr.rtype() == Rtype::OPT {
} else {
let rr = rr
.into_record::<UnknownRecordData<_>>()?
.expect("UnknownRecordData should not fail");
.into_record::<AllRecordData<_, ParsedName<_>>>()?
.expect("AllRecordData should not fail");
target.push(rr).expect("push should not fail");
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/rdata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ rdata_types! {
Ds<O>,
}
}
naptr::{
zone {
Naptr<O, N>,
}
}
nsec3::{
zone {
Nsec3<O>,
Expand Down
Loading

0 comments on commit e1c1db8

Please sign in to comment.