#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
pub mod config;
pub mod dns;
pub mod http;
pub mod metrics;
pub mod server;
pub mod state;
mod store;
mod util;
pub use store::ZoneStore;
#[cfg(test)]
mod tests {
use std::{
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
time::Duration,
};
use anyhow::Result;
use iroh::{
discovery::pkarr::PkarrRelayClient, dns::DnsResolver, node_info::NodeInfo, RelayUrl,
SecretKey,
};
use pkarr::{SignedPacket, Timestamp};
use testresult::TestResult;
use tracing_test::traced_test;
use crate::{
config::BootstrapOption,
server::Server,
store::{PacketSource, ZoneStoreOptions},
util::PublicKeyBytes,
ZoneStore,
};
const DNS_TIMEOUT: Duration = Duration::from_secs(1);
#[tokio::test]
#[traced_test]
async fn pkarr_publish_dns_resolve() -> Result<()> {
let (server, nameserver, http_url) = Server::spawn_for_tests().await?;
let pkarr_relay_url = {
let mut url = http_url.clone();
url.set_path("/pkarr");
url
};
let signed_packet = {
use pkarr::dns;
let keypair = pkarr::Keypair::random();
let mut packet = dns::Packet::new_reply(0);
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::TXT("hi0".try_into()?),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("_hello").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::TXT("hi1".try_into()?),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("_hello.world").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::TXT("hi2".try_into()?),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("multiple").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::TXT("hi3".try_into()?),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("multiple").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::TXT("hi4".try_into()?),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::A(Ipv4Addr::LOCALHOST.into()),
));
packet.answers.push(dns::ResourceRecord::new(
dns::Name::new("foo.bar.baz").unwrap(),
dns::CLASS::IN,
30,
dns::rdata::RData::AAAA(Ipv6Addr::LOCALHOST.into()),
));
SignedPacket::new(&keypair, &packet.answers, Timestamp::now())?
};
let pkarr_client = pkarr::Client::builder()
.no_default_network()
.relays(&[pkarr_relay_url])?
.build()?;
pkarr_client.publish(&signed_packet, None).await?;
use hickory_server::proto::rr::Name;
let pubkey = signed_packet.public_key().to_z32();
let resolver = test_resolver(nameserver);
let name = Name::from_utf8(format!("{pubkey}."))?;
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
assert_eq!(records, vec!["hi0".to_string()]);
let name = Name::from_utf8(format!("_hello.{pubkey}."))?;
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
assert_eq!(records, vec!["hi1".to_string()]);
let name = Name::from_utf8(format!("_hello.world.{pubkey}."))?;
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
assert_eq!(records, vec!["hi2".to_string()]);
let name = Name::from_utf8(format!("multiple.{pubkey}."))?;
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
assert_eq!(records, vec!["hi3".to_string(), "hi4".to_string()]);
let name = Name::from_utf8(format!("{pubkey}."))?;
let res = resolver.lookup_ipv4(name, DNS_TIMEOUT).await?;
let records = res.collect::<Vec<_>>();
assert_eq!(records, vec![Ipv4Addr::LOCALHOST]);
let name = Name::from_utf8(format!("foo.bar.baz.{pubkey}."))?;
let res = resolver.lookup_ipv6(name, DNS_TIMEOUT).await?;
let records = res.collect::<Vec<_>>();
assert_eq!(records, vec![Ipv6Addr::LOCALHOST]);
server.shutdown().await?;
Ok(())
}
#[tokio::test]
#[traced_test]
async fn integration_smoke() -> Result<()> {
let (server, nameserver, http_url) = Server::spawn_for_tests().await?;
let pkarr_relay = {
let mut url = http_url.clone();
url.set_path("/pkarr");
url
};
let origin = "irohdns.example.";
let secret_key = SecretKey::generate(rand::thread_rng());
let node_id = secret_key.public();
let pkarr = PkarrRelayClient::new(pkarr_relay);
let relay_url: RelayUrl = "https://relay.example.".parse()?;
let node_info = NodeInfo::new(node_id).with_relay_url(Some(relay_url.clone()));
let signed_packet = node_info.to_pkarr_signed_packet(&secret_key, 30)?;
pkarr.publish(&signed_packet).await?;
let resolver = test_resolver(nameserver);
let res = resolver.lookup_node_by_id(&node_id, origin).await?;
assert_eq!(res.node_id, node_id);
assert_eq!(res.relay_url(), Some(&relay_url));
server.shutdown().await?;
Ok(())
}
#[tokio::test]
#[traced_test]
async fn store_eviction() -> TestResult<()> {
let options = ZoneStoreOptions {
eviction: Duration::from_millis(100),
eviction_interval: Duration::from_millis(100),
max_batch_time: Duration::from_millis(100),
..Default::default()
};
let store = ZoneStore::in_memory(options, Default::default())?;
let signed_packet = random_signed_packet()?;
let key = PublicKeyBytes::from_signed_packet(&signed_packet);
store
.insert(signed_packet, PacketSource::PkarrPublish)
.await?;
tokio::time::sleep(Duration::from_secs(1)).await;
for _ in 0..10 {
let entry = store.get_signed_packet(&key).await?;
if entry.is_none() {
return Ok(());
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
panic!("store did not evict packet");
}
#[tokio::test]
#[traced_test]
async fn integration_mainline() -> Result<()> {
let testnet = pkarr::mainline::Testnet::new_async(5).await?;
let bootstrap = testnet.bootstrap.clone();
let (server, nameserver, _http_url) =
Server::spawn_for_tests_with_options(Some(BootstrapOption::Custom(bootstrap)), None)
.await?;
let origin = "irohdns.example.";
let secret_key = SecretKey::generate(rand::thread_rng());
let node_id = secret_key.public();
let relay_url: RelayUrl = "https://relay.example.".parse()?;
let node_info = NodeInfo::new(node_id).with_relay_url(Some(relay_url.clone()));
let signed_packet = node_info.to_pkarr_signed_packet(&secret_key, 30)?;
let pkarr = pkarr::Client::builder()
.no_default_network()
.dht(|builder| builder.bootstrap(&testnet.bootstrap))
.build()?;
pkarr.publish(&signed_packet, None).await?;
let resolver = test_resolver(nameserver);
let res = resolver.lookup_node_by_id(&node_id, origin).await?;
assert_eq!(res.node_id, node_id);
assert_eq!(res.relay_url(), Some(&relay_url));
server.shutdown().await?;
Ok(())
}
fn test_resolver(nameserver: SocketAddr) -> DnsResolver {
DnsResolver::with_nameserver(nameserver)
}
fn random_signed_packet() -> Result<SignedPacket> {
let secret_key = SecretKey::generate(rand::thread_rng());
let node_id = secret_key.public();
let relay_url: RelayUrl = "https://relay.example.".parse()?;
let node_info = NodeInfo::new(node_id).with_relay_url(Some(relay_url.clone()));
node_info.to_pkarr_signed_packet(&secret_key, 30)
}
}