added ::new prototype aand read-zone ldns args

This commit is contained in:
withjannisNLnetLabs
2026-05-14 15:41:48 +02:00
parent 2224da70c9
commit bb38ac3d78
2 changed files with 224 additions and 51 deletions
+1
View File
@@ -39,6 +39,7 @@ domain = { git = "https://github.com/NLnetLabs/domain.git", branch = "main", fea
"tokio-stream",
"tsig",
"unstable-client-transport",
"unstable-new",
"unstable-sign",
"unstable-validator",
"unstable-zonetree"
+223 -51
View File
@@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
use bytes::BufMut;
use clap::Parser;
use domain::base::name::FlattenInto;
use domain::base::zonefile_fmt::ZonefileFmt;
use domain::base::{CanonicalOrd, Record, ToName};
@@ -18,25 +19,77 @@ use domain::rdata::ZoneRecordData;
use domain::zonefile::inplace::{self, Entry};
use domain::zonetree::types::StoredRecordData;
use domain::zonetree::{StoredName, StoredRecord};
use lexopt::Arg;
use rayon::slice::ParallelSliceMut;
use crate::env::{Env, Stream};
use crate::error::{Context, Error};
use crate::{Args, DISPLAY_KIND};
use super::{parse_os, Command, LdnsCommand};
use super::{Command, LdnsCommand};
//------------ Constants -----------------------------------------------------
//------------ ReadZone ------------------------------------------------------
#[derive(Clone, Debug, clap::Args, PartialEq)]
#[derive(Clone, Debug, clap::Parser, PartialEq)]
// TODO
#[clap(after_help = "Read-Zone dnst HELP")]
pub struct ReadZone {
/// Origin for the zone (REQUIRED).
#[arg(short = 'o', value_name = "domain", required = true)]
// -----------------------------------------------------------------------
// Original ldns-read-zone options in ldns-read-zone -h order:
// -----------------------------------------------------------------------
/// Canonicalize all resource records in the zone before printing
#[arg(short = 'c', default_value_t = false)]
canonicalize: bool,
/// Only print DNSSEC data from the zone. This option skips every record
/// that is not of type NSEC, NSEC3, RRSIG or DNSKEY. DS records are not
/// printed.
#[arg(short = 'd', default_value_t = false)]
print_only_dnssec: bool,
/// Print a (null) for the RRSIG inception, expiry and key data. This option
/// can be used when comparing different signing systems that use the same
/// DNSKEYs for signing but would have a slightly different timings/jitter.
#[arg(short = 'O', default_value_t = false)]
print_rrsig_null: bool,
/// Do not print the SOA record
#[arg(short = 'n', default_value_t = false)]
print_not_soa: bool,
/// Pad the SOA serial number with spaces so the number and the spaces
/// together take ten characters. This is useful for in file serial number
/// increments.
#[arg(short = 'p', default_value_t = false)]
pad_soa_serial: bool,
/// Strip DNSSEC data from the zone. This option skips every record that is
/// of type NSEC, NSEC3, RRSIG or DNSKEY. DS records are still printed.
#[arg(short = 's', default_value_t = false)]
print_not_dnssec: bool,
/// Set serial number to the given number, or when preceded by a sign,
/// offset the exisiting number with it. When giving the literal strings
/// YYYYMMDDxx or unixtime, the serial number is tried to be reset in
/// datecounter or in unixtime format respectively. Though is the updated
/// serial number is smaller than the original one, the original one is
/// simply increased by one.
#[arg(short = 'S', required = false)]
manipulate_serial: Option<String>,
// TODO: I don't think that -z implies -c. Because you can sort the records
// even though you print them in the original form i.e. with some capital
// letters.
/// Sort the zone before printing (this implies -c)
#[arg(short = 'z', default_value_t = false)]
canonical_sort: bool,
// -----------------------------------------------------------------------
// Extra options not supported by the original ldns-signzone:
// -----------------------------------------------------------------------
/// Origin for the zone.
#[arg(short = 'o', value_name = "domain", required = false)]
origin: Option<StoredName>,
// -----------------------------------------------------------------------
@@ -55,8 +108,48 @@ pub struct ReadZone {
}
// TODO
const LDNS_HELP: &str = r###"
Read-Zone LDNS HELP
const LDNS_HELP: &str = r###"ldns-read-zone [OPTIONS] <zonefile>
Reads the zonefile and prints it.
The RR count of the zone is printed to stderr.
-0 zeroize timestamps and signature in RRSIG records.
-b include Bubble Babble encoding of DS's.
-c canonicalize all rrs in the zone.
-d only show DNSSEC data from the zone
-e <rr type>
Do not print RRs of the given <rr type>.
This option may be given multiple times.
-e is not meant to be used together with -E.
-E <rr type>
Print only RRs of the given <rr type>.
This option may be given multiple times.
-E is not meant to be used together with -e.
-h show this text
-n do not print the SOA record
-p prepend SOA serial with spaces so it takes exactly ten characters.
-s strip DNSSEC data from the zone
-S [[+|-]<number> | YYYYMMDDxx | unixtime ]
Set serial number to <number> or, when preceded by a sign,
offset the existing number with <number>. With YYYYMMDDxx
the serial is formatted as a datecounter, and with unixtime as
the number of seconds since 1-1-1970. However, on serial
number decrease, +1 is used in stead. (implies -s)
-u <rr type>
Mark <rr type> for printing in unknown type format.
This option may be given multiple times.
-u is not meant to be used together with -U.
-U <rr type>
Mark <rr type> for not printing in unknown type format.
This option may be given multiple times.
The first occurrence of the -U option marks all RR types for
printing in unknown type format except for the given <rr type>.
Subsequent -U options will clear the mark for those <rr type>s
too, so that only the given <rr type>s will be printed in the
presentation format specific for those <rr type>s.
-U is not meant to be used together with -u.
-v shows the version and exits
-z sort the zone (implies -c).
if no file is given standard input is read
"###;
impl LdnsCommand for ReadZone {
@@ -64,50 +157,72 @@ impl LdnsCommand for ReadZone {
const HELP: &'static str = LDNS_HELP;
const COMPATIBLE_VERSION: &'static str = "1.8.4"; // TODO
fn parse_ldns<I: IntoIterator<Item = OsString>>(args: I) -> Result<Args, Error> {
let mut origin = Option::<StoredName>::None;
let mut zonefile = Option::<PathBuf>::None;
let mut parser = lexopt::Parser::from_args(args);
while let Some(arg) = parser.next()? {
match arg {
Arg::Short('o') => {
let val = parser.value()?;
origin = Some(parse_os("-o", &val)?);
}
Arg::Value(val) => {
if zonefile.is_none() {
zonefile = Some(parse_os("zonefile", &val)?);
}
}
Arg::Short(x) => return Err(format!("Invalid short option: -{x}").into()),
Arg::Long(x) => {
return Err(format!("Long options are not supported, but `--{x}` given").into())
}
}
}
let Some(zonefile_path) = zonefile else {
return Err("Missing zonefile argument".into());
};
fn parse_ldns<I: IntoIterator<Item = OsString>>(unargs: I) -> Result<Args, Error> {
let args = ReadZone::parse();
println!("{:?}", args);
Ok(Args::from(Command::ReadZone(Self {
origin,
zonefile_path,
invoked_as_ldns: true,
canonicalize: args.canonicalize,
print_only_dnssec: args.print_only_dnssec,
print_rrsig_null: args.print_rrsig_null,
print_not_soa: args.print_not_soa,
pad_soa_serial: args.pad_soa_serial,
print_not_dnssec: args.print_not_dnssec,
manipulate_serial: args.manipulate_serial,
canonical_sort: args.canonical_sort,
origin: args.origin,
zonefile_path: args.zonefile_path,
invoked_as_ldns: args.invoked_as_ldns,
})))
// let mut parser = lexopt::Parser::from_args(args);
// while let Some(arg) = parser.next()? {
// match arg {
// Arg::Short('o') => {
// let val = parser.value()?;
// origin = Some(parse_os("-o", &val)?);
// }
// Arg::Value(val) => {
// if zonefile.is_none() {
// zonefile = Some(parse_os("zonefile", &val)?);
// }
// }
// Arg::Short(x) => return Err(format!("Invalid short option: -{x}").into()),
// Arg::Long(x) => {
// return Err(format!("Long options are not supported, but `--{x}` given").into())
// }
// }
// }
// let Some(zonefile_path) = zonefile else {
// return Err("Missing zonefile argument".into());
// };
// Ok(Args::from(Command::ReadZone(Self {
// origin,
// zonefile_path,
// invoked_as_ldns: true,
// })))
}
}
impl ReadZone {
pub fn execute(self, env: impl Env) -> Result<(), Error> {
// Read the zone file.
let records = self.load_zone(&env.in_cwd(&self.zonefile_path))?;
let records: SortedRecords<
domain::base::Name<bytes::Bytes>,
ZoneRecordData<bytes::Bytes, domain::base::Name<bytes::Bytes>>,
MultiThreadedSorter,
> = self.load_zone(&env.in_cwd(&self.zonefile_path))?;
let mut writer: FileOrStdout<BufWriter<File>, _> = FileOrStdout::Stdout(env.stdout());
for rr in records.iter() {
if self.canonicalize {
// TODO: there is no way to modify the owner inplace?
let name: domain::base::Name<bytes::Bytes> = rr.owner().to_canonical_name();
println!("{:?}", name);
}
self.writeln_rr(&mut writer, rr)?;
}
@@ -257,9 +372,6 @@ impl domain::dnssec::sign::records::Sorter for MultiThreadedSorter {
mod test {
use std::str::FromStr;
use domain::base::CanonicalOrd;
use pretty_assertions::assert_eq;
use crate::commands::readzone::PathBuf;
use crate::commands::readzone::StoredName;
use crate::commands::Command;
@@ -281,16 +393,21 @@ mod test {
let cmd = FakeCmd::new(["dnst", "read-zone"]);
let base = ReadZone {
canonicalize: false,
print_only_dnssec: false,
print_rrsig_null: false,
manipulate_serial: None,
origin: Some(StoredName::from_str("example.org").unwrap()),
pad_soa_serial: false,
print_not_dnssec: false,
print_not_soa: false,
canonical_sort: false,
zonefile_path: PathBuf::from("example.org.zone"),
invoked_as_ldns: false,
};
// Check the defaults
assert_eq!(
parse(cmd.args(["-oexample.org", "example.org.zone"])),
base
);
assert_eq!(parse(cmd.args(["-oexample.org", "example.org.zone"])), base);
}
#[test]
@@ -298,6 +415,7 @@ mod test {
let res1 = FakeCmd::new([
"dnst",
"read-zone",
"-c",
"-o example.com.",
"test-data/example.org",
])
@@ -310,12 +428,66 @@ mod test {
#[test]
fn name_canonicalization() {
use domain::base::name::Name;
let name1: Name<bytes::Bytes> = Name::<bytes::Bytes>::from_str("0.example.com.").unwrap();
println!("{:?}", "0.example.com.".as_bytes());
let name2: Name<bytes::Bytes> = Name::<bytes::Bytes>::from_str("1.example.com.").unwrap();
println!("{:?}", "1.example.com.".as_bytes());
use bytes::Bytes;
println!("{:?}", name1.canonical_cmp(&name2));
use domain::base::iana::Class;
use domain::base::name::Name;
use domain::base::Record;
use domain::base::Ttl;
use domain::new;
use domain::rdata::Mx;
use domain::rdata::ZoneRecordData;
let owner: Name<bytes::Bytes> = Name::<bytes::Bytes>::from_str("EXAMPLE.com.").unwrap();
let class = Class::IN;
let ttl = Ttl::from_hours(1);
let exchange: Name<bytes::Bytes> =
Name::<bytes::Bytes>::from_str("mail.example.com.").unwrap();
let data: ZoneRecordData<Bytes, Name<Bytes>> = ZoneRecordData::Mx(Mx::new(10, exchange));
let record: Record<Name<Bytes>, ZoneRecordData<Bytes, Name<Bytes>>>;
record = Record::new(owner, class, ttl, data);
let name: new::base::name::NameBuf = "EXAMPLE.com".parse().unwrap();
let rtype: new::base::RType = new::base::RType::MX;
let rclass = new::base::RClass::IN;
let ttl = new::base::TTL::from(3600);
let preference = new::base::wire::U16::new(10);
let exchange: new::base::name::NameBuf = "MAIL.example.com".parse().unwrap();
let rdata: new::rdata::RecordData<new::base::name::NameBuf> =
new::rdata::RecordData::Mx(new::rdata::Mx {
preference,
exchange,
});
let new_record = new::base::Record::new(
name,
rtype.clone(),
rclass.clone(),
ttl.clone(),
rdata.clone(),
);
let name: new::base::name::NameBuf = "example.com".parse().unwrap();
let other_new_record = new::base::Record::new(name, rtype, rclass, ttl, rdata);
println!(
"{:?} {:?} {:?}",
new_record.rname,
new_record.rname.cmp(&other_new_record.rname),
other_new_record.rname
);
let mut target = Vec::<u8>::new();
record
.compose_canonical(&mut target)
.expect("This is fine!");
println!("{:?}", record);
println!("{:?}", String::from_utf8(target));
}
}