#![cfg(feature = "dnssec")] use std::{ future::Future, net::{Ipv4Addr, Ipv6Addr, SocketAddr}, str::FromStr, }; use futures_executor::block_on; use hickory_proto::{ op::{update_message, Header, Message, Query, ResponseCode}, rr::{ dnssec::{Algorithm, PublicKey, SigSigner, SupportedAlgorithms, Verifier}, rdata::{A as A4, AAAA}, DNSClass, Name, RData, Record, RecordSet, RecordType, }, serialize::binary::{BinDecodable, BinEncodable}, xfer::Protocol, }; use hickory_server::{ authority::{ AuthLookup, Authority, DnssecAuthority, LookupError, LookupOptions, MessageRequest, UpdateResult, }, server::RequestInfo, }; const TEST_HEADER: &Header = &Header::new(); fn update_authority>( mut message: Message, key: &SigSigner, authority: &mut A, ) -> UpdateResult { message.finalize(key, 1).expect("failed to sign message"); let message = message.to_bytes().unwrap(); let request = MessageRequest::from_bytes(&message).unwrap(); block_on(authority.update(&request)) } pub fn test_create>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("create.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(127, 0, 0, 10))); let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let query = Query::query(name, RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); match lookup .into_iter() .next() .expect("A record not found in authority") .data() { RData::A(ip) => assert_eq!(A4::new(127, 0, 0, 10), *ip), _ => panic!("wrong rdata type returned"), } // trying to create again should error let mut message = update_message::create(record.into(), Name::from_str("example.com.").unwrap(), true); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::YXRRSet ); } } pub fn test_create_multi>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("create-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // create a record let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); let mut record2 = record.clone(); record2.set_data(RData::A(A4::new(100, 10, 100, 11))); let record2 = record2; let mut rrset = RecordSet::from(record.clone()); rrset.insert(record2.clone(), 0); let rrset = rrset; let message = update_message::create(rrset.clone(), Name::from_str("example.com.").unwrap(), true); assert!(update_authority(message, key, &mut authority).expect("create failed")); let query = Query::query(name, RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); // trying to create again should error let message = update_message::create(rrset, Name::from_str("example.com.").unwrap(), true); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::YXRRSet ); } } pub fn test_append>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("append.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let mut message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::NXRRSet ); // next append to a non-existent RRset let message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), false, true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); // verify record contents let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == record)); // will fail if already set and not the same value. let mut record2 = record.clone(); record2.set_data(RData::A(A4::new(101, 11, 101, 11))); let message = update_message::append( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); // show that appending the same thing again is ok, but doesn't add any records let message = update_message::append( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(!update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name, RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); } } pub fn test_append_multi>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("append-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // next append to a non-existent RRset let message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), false, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); // will fail if already set and not the same value. let mut record2 = record.clone(); record2.set_data(RData::A(A4::new(101, 11, 101, 11))); let mut record3 = record.clone(); record3.set_data(RData::A(A4::new(101, 11, 101, 12))); // build the append set let mut rrset = RecordSet::from(record2.clone()); rrset.insert(record3.clone(), 0); let message = update_message::append( rrset.clone(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 3); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); assert!(lookup.iter().any(|rr| *rr == record3)); // show that appending the same thing again is ok, but doesn't add any records // TODO: technically this is a test for the Server, not client... let message = update_message::append( rrset.clone(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(!update_authority(message, key, &mut authority).expect("append failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 3); assert!(lookup.iter().any(|rr| *rr == record)); assert!(lookup.iter().any(|rr| *rr == record2)); assert!(lookup.iter().any(|rr| *rr == record3)); } } pub fn test_compare_and_swap>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("compare-and-swap.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // create a record let mut record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); let record = record; let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let current = record; let mut new = current.clone(); new.set_data(RData::A(A4::new(101, 11, 101, 11))); let new = new; let message = update_message::compare_and_swap( current.clone().into(), new.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("compare_and_swap failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == new)); assert!(!lookup.iter().any(|rr| *rr == current)); // check the it fails if tried again. let mut not = new.clone(); not.set_data(RData::A(A4::new(102, 12, 102, 12))); let not = not; let message = update_message::compare_and_swap( current.into(), not.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::NXRRSet ); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == new)); assert!(!lookup.iter().any(|rr| *rr == not)); } } pub fn test_compare_and_swap_multi>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("compare-and-swap-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // create a record let mut current = RecordSet::with_ttl(name.clone(), RecordType::A, 8); let current1 = current .new_record(&RData::A(A4::new(100, 10, 100, 10))) .clone(); let current2 = current .new_record(&RData::A(A4::new(100, 10, 100, 11))) .clone(); let current = current; let mut message = update_message::create( current.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let mut new = RecordSet::with_ttl(current.name().clone(), current.record_type(), current.ttl()); let new1 = new.new_record(&RData::A(A4::new(100, 10, 101, 10))).clone(); let new2 = new.new_record(&RData::A(A4::new(100, 10, 101, 11))).clone(); let new = new; let mut message = update_message::compare_and_swap( current.clone(), new.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("compare_and_swap failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == new1)); assert!(lookup.iter().any(|rr| *rr == new2)); assert!(!lookup.iter().any(|rr| *rr == current1)); assert!(!lookup.iter().any(|rr| *rr == current2)); // check the it fails if tried again. let mut not = new1.clone(); not.set_data(RData::A(A4::new(102, 12, 102, 12))); let not = not; let message = update_message::compare_and_swap( current.clone(), not.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert_eq!( update_authority(message, key, &mut authority).unwrap_err(), ResponseCode::NXRRSet ); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(lookup.iter().any(|rr| *rr == new1)); assert!(!lookup.iter().any(|rr| *rr == not)); } } pub fn test_delete_by_rdata>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("test-delete-by-rdata.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let record1 = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let mut message = update_message::delete_by_rdata( record1.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); // next create to a non-existent RRset let mut message = update_message::create( record1.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); let mut record2 = record1.clone(); record2.set_data(RData::A(A4::new(101, 11, 101, 11))); let message = update_message::append( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); // verify record contents let message = update_message::delete_by_rdata( record2.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 1); assert!(lookup.iter().any(|rr| *rr == record1)); } } pub fn test_delete_by_rdata_multi>( mut authority: A, keys: &[SigSigner], ) { let name = Name::from_str("test-delete-by-rdata-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let mut rrset = RecordSet::with_ttl(name.clone(), RecordType::A, 8); let record1 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 10))) .clone(); let record2 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 11))) .clone(); let record3 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 12))) .clone(); let record4 = rrset .new_record(&RData::A(A4::new(100, 10, 100, 13))) .clone(); let rrset = rrset; // first check the must_exist option let message = update_message::delete_by_rdata( rrset.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); // next create to a non-existent RRset let message = update_message::create(rrset.clone(), Name::from_str("example.com.").unwrap(), true); assert!(update_authority(message, key, &mut authority).expect("create failed")); // append a record let mut rrset = RecordSet::with_ttl(name.clone(), RecordType::A, 8); let record1 = rrset.new_record(record1.data()).clone(); let record3 = rrset.new_record(record3.data()).clone(); let rrset = rrset; let message = update_message::append( rrset.clone(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(!update_authority(message, key, &mut authority).expect("append failed")); // verify record contents let message = update_message::delete_by_rdata( rrset.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_by_rdata failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())).unwrap(); assert_eq!(lookup.iter().count(), 2); assert!(!lookup.iter().any(|rr| *rr == record1)); assert!(lookup.iter().any(|rr| *rr == record2)); assert!(!lookup.iter().any(|rr| *rr == record3)); assert!(lookup.iter().any(|rr| *rr == record4)); } } pub fn test_delete_rrset>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("compare-and-swap-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let mut record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let message = update_message::delete_rrset( record.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_rrset failed")); // next create to a non-existent RRset let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let mut record = record.clone(); record.set_data(RData::A(A4::new(101, 11, 101, 11))); let message = update_message::append( record.clone().into(), Name::from_str("example.com.").unwrap(), true, true, ); assert!(update_authority(message, key, &mut authority).expect("append failed")); // verify record contents let message = update_message::delete_rrset( record.clone(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("delete_rrset failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); assert_eq!( *lookup.unwrap_err().as_response_code().unwrap(), ResponseCode::NXDomain ); } } pub fn test_delete_all>(mut authority: A, keys: &[SigSigner]) { let name = Name::from_str("compare-and-swap-multi.example.com.").unwrap(); for key in keys { let name = Name::from_str(key.algorithm().as_str()) .unwrap() .append_name(&name) .unwrap(); // append a record let mut record = Record::from_rdata(name.clone(), 8, RData::A(A4::new(100, 10, 100, 10))); // first check the must_exist option let message = update_message::delete_all( record.name().clone(), Name::from_str("example.com.").unwrap(), DNSClass::IN, true, ); assert!(!update_authority(message, key, &mut authority).expect("delete_all failed")); // next create to a non-existent RRset let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); let mut record = record.clone(); record.set_data(RData::AAAA(AAAA::new(1, 2, 3, 4, 5, 6, 7, 8))); let message = update_message::create( record.clone().into(), Name::from_str("example.com.").unwrap(), true, ); assert!(update_authority(message, key, &mut authority).expect("create failed")); // verify record contents let message = update_message::delete_all( record.name().clone(), Name::from_str("example.com.").unwrap(), DNSClass::IN, true, ); assert!(update_authority(message, key, &mut authority).expect("delete_all failed")); let query = Query::query(name.clone(), RecordType::A).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); assert_eq!( *lookup.unwrap_err().as_response_code().unwrap(), ResponseCode::NXDomain ); let query = Query::query(name.clone(), RecordType::AAAA).into(); let request_info = RequestInfo::new( SocketAddr::from((Ipv4Addr::LOCALHOST, 53)), Protocol::Udp, TEST_HEADER, &query, ); let lookup = block_on(authority.search(request_info, LookupOptions::default())); assert_eq!( *lookup.unwrap_err().as_response_code().unwrap(), ResponseCode::NXDomain ); } } pub fn add_auth(authority: &mut A) -> Vec { use hickory_dns::dnssec::KeyConfig; use hickory_proto::rr::dnssec::rdata::key::KeyUsage; let update_name = Name::from_str("update") .unwrap() .append_domain(&authority.origin().to_owned().into()) .unwrap(); let mut keys = Vec::::new(); // TODO: support RSA signing with ring // rsa #[cfg(feature = "dnssec-openssl")] { let key_config = KeyConfig { key_path: "../tests/test-data/test_configs/dnssec/rsa_2048.pem".to_string(), password: Some("123456".to_string()), algorithm: Algorithm::RSASHA512.to_string(), signer_name: Some(update_name.to_string()), is_zone_signing_key: Some(true), is_zone_update_auth: Some(false), }; let signer = key_config .try_into_signer(update_name.clone()) .expect("failed to read key_config"); let public_key = signer .key() .to_public_key() .expect("failed to get public key") .to_sig0key_with_usage(Algorithm::RSASHA512, KeyUsage::Host); block_on(authority.add_update_auth_key(update_name.clone(), public_key)) .expect("failed to add signer to zone"); keys.push(signer); } // // TODO: why are ecdsa tests failing in this context? // // ecdsa_p256 // { // let key_config = KeyConfig { // key_path: "tests/test-data/test_configs/dnssec/ecdsa_p256.pem".to_string(), // password: None, // algorithm: Algorithm::ECDSAP256SHA256.to_string(), // signer_name: Some(signer_name.clone().to_string()), // is_zone_signing_key: Some(true), // is_zone_update_auth: Some(false), // }; // let signer = key_config.try_into_signer(signer_name.clone()).expect("failed to read key_config"); // keys.push(signer.to_dnskey().expect("failed to create DNSKEY")); // authority.add_zone_signing_key(signer).expect("failed to add signer to zone"); // authority.secure_zone().expect("failed to sign zone"); // } // // ecdsa_p384 // { // let key_config = KeyConfig { // key_path: "../../tests/test-data/test_configs/dnssec/ecdsa_p384.pem".to_string(), // password: None, // algorithm: Algorithm::ECDSAP384SHA384.to_string(), // signer_name: Some(signer_name.clone().to_string()), // is_zone_signing_key: Some(true), // is_zone_update_auth: Some(false), // }; // let signer = key_config.try_into_signer(signer_name.clone()).expect("failed to read key_config"); // keys.push(signer.to_dnskey().expect("failed to create DNSKEY")); // authority.add_zone_signing_key(signer).expect("failed to add signer to zone"); // authority.secure_zone().expect("failed to sign zone"); // } // ed 25519 #[cfg(feature = "dnssec-ring")] { let key_config = KeyConfig { key_path: "../tests/test-data/test_configs/dnssec/ed25519.pk8".to_string(), password: None, algorithm: Algorithm::ED25519.to_string(), signer_name: Some(update_name.to_string()), is_zone_signing_key: Some(true), is_zone_update_auth: Some(false), }; let signer = key_config .try_into_signer(update_name.clone()) .expect("failed to read key_config"); let public_key = signer .key() .to_public_key() .expect("failed to get public key") .to_sig0key_with_usage(Algorithm::ED25519, KeyUsage::Host); block_on(authority.add_update_auth_key(update_name, public_key)) .expect("failed to add signer to zone"); keys.push(signer); } keys } macro_rules! define_update_test { ($new:expr; $( $f:ident, )*) => { $( #[test] fn $f () { let mut authority = $new("../tests/test-data/test_configs/example.com.zone", module_path!(), stringify!($f)); let keys = crate::authority_battery::dynamic_update::add_auth(&mut authority); crate::authority_battery::dynamic_update::$f(authority, &keys); } )* } } macro_rules! dynamic_update { ($name:ident, $new:expr) => { #[cfg(test)] mod dynamic_update { mod $name { define_update_test!($new; test_create, test_create_multi, test_append, test_append_multi, test_compare_and_swap, test_compare_and_swap_multi, test_delete_by_rdata, test_delete_by_rdata_multi, test_delete_rrset, test_delete_all, ); } } }; }