1#![deny(missing_docs, rustdoc::broken_intra_doc_links, unreachable_pub)]
26
27pub mod config;
28mod dns;
29mod http;
30mod metrics;
31mod server;
32mod state;
33mod store;
34mod util;
35
36pub use crate::{metrics::Metrics, server::Server};
37
38#[cfg(test)]
39mod tests {
40 use std::{
41 net::{Ipv4Addr, Ipv6Addr, SocketAddr},
42 time::Duration,
43 };
44
45 use iroh::{
46 RelayUrl, SecretKey,
47 address_lookup::PkarrRelayClient,
48 dns::DnsResolver,
49 endpoint_info::EndpointInfo,
50 tls::{CaTlsConfig, default_provider},
51 };
52 use iroh_dns::pkarr::SignedPacket;
53 use mainline::{DhtBuilder, MutableItem, Testnet};
54 use n0_error::{Result, StdResultExt};
55 use n0_tracing_test::traced_test;
56 use rand::{CryptoRng, RngExt, SeedableRng};
57
58 use crate::{
59 config::BootstrapOption,
60 server::Server,
61 store::{Options, PacketSource, ZoneStore},
62 util::PublicKeyBytes,
63 };
64
65 const DNS_TIMEOUT: Duration = Duration::from_secs(2);
66
67 #[tokio::test]
68 #[traced_test]
69 async fn pkarr_publish_dns_resolve() -> Result {
70 use simple_dns::{CLASS, Name as DnsName, Packet, ResourceRecord, rdata};
71
72 let dir = tempfile::tempdir()?;
73 let server = Server::spawn_for_tests(dir.path()).await?;
74 let pkarr_relay_url = {
75 let mut url = server.http_url().expect("http is bound");
76 url.set_path("/pkarr");
77 url
78 };
79
80 let secret_key = SecretKey::generate();
82 let origin = secret_key.public().to_z32();
83
84 let mut packet = Packet::new_reply(0);
85 packet.answers.push(ResourceRecord::new(
87 DnsName::new_unchecked(&origin).into_owned(),
88 CLASS::IN,
89 30,
90 rdata::RData::TXT("hi0".try_into().unwrap()),
91 ));
92 packet.answers.push(ResourceRecord::new(
94 DnsName::new_unchecked(&format!("_hello.{origin}")).into_owned(),
95 CLASS::IN,
96 30,
97 rdata::RData::TXT("hi1".try_into().unwrap()),
98 ));
99 packet.answers.push(ResourceRecord::new(
101 DnsName::new_unchecked(&format!("_hello.world.{origin}")).into_owned(),
102 CLASS::IN,
103 30,
104 rdata::RData::TXT("hi2".try_into().unwrap()),
105 ));
106 packet.answers.push(ResourceRecord::new(
108 DnsName::new_unchecked(&format!("multiple.{origin}")).into_owned(),
109 CLASS::IN,
110 30,
111 rdata::RData::TXT("hi3".try_into().unwrap()),
112 ));
113 packet.answers.push(ResourceRecord::new(
114 DnsName::new_unchecked(&format!("multiple.{origin}")).into_owned(),
115 CLASS::IN,
116 30,
117 rdata::RData::TXT("hi4".try_into().unwrap()),
118 ));
119 packet.answers.push(ResourceRecord::new(
121 DnsName::new_unchecked(&origin).into_owned(),
122 CLASS::IN,
123 30,
124 rdata::RData::A(Ipv4Addr::LOCALHOST.into()),
125 ));
126 packet.answers.push(ResourceRecord::new(
128 DnsName::new_unchecked(&format!("foo.bar.baz.{origin}")).into_owned(),
129 CLASS::IN,
130 30,
131 rdata::RData::AAAA(Ipv6Addr::LOCALHOST.into()),
132 ));
133
134 let encoded = packet.build_bytes_vec_compressed().anyerr()?;
136 let timestamp = std::time::SystemTime::now()
137 .duration_since(std::time::UNIX_EPOCH)
138 .unwrap()
139 .as_micros() as u64;
140 let signable = {
141 let mut s = format!("3:seqi{}e1:v{}:", timestamp, encoded.len()).into_bytes();
142 s.extend(&encoded);
143 s
144 };
145 let signature = secret_key.sign(&signable);
146 let mut raw = Vec::with_capacity(104 + encoded.len());
147 raw.extend_from_slice(secret_key.public().as_bytes());
148 raw.extend_from_slice(&signature.to_bytes());
149 raw.extend_from_slice(×tamp.to_be_bytes());
150 raw.extend_from_slice(&encoded);
151 let signed_packet = SignedPacket::from_bytes(&raw).anyerr()?;
152
153 let tls_config = CaTlsConfig::default()
155 .client_config(default_provider())
156 .expect("infallible");
157 let pkarr_client =
158 PkarrRelayClient::new(pkarr_relay_url, tls_config, DnsResolver::default());
159 pkarr_client.publish(&signed_packet).await?;
160
161 use hickory_server::proto::rr::Name;
162 let pubkey = origin;
163 let resolver = test_resolver(server.dns_addr());
164
165 let name = Name::from_utf8(format!("{pubkey}.")).anyerr()?;
167 let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
168 let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
169 assert_eq!(records, vec!["hi0".to_string()]);
170
171 let name = Name::from_utf8(format!("_hello.{pubkey}.")).anyerr()?;
173 let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
174 let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
175 assert_eq!(records, vec!["hi1".to_string()]);
176
177 let name = Name::from_utf8(format!("_hello.world.{pubkey}.")).anyerr()?;
179 let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
180 let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
181 assert_eq!(records, vec!["hi2".to_string()]);
182
183 let name = Name::from_utf8(format!("multiple.{pubkey}.")).anyerr()?;
185 let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
186 let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
187 assert_eq!(records, vec!["hi3".to_string(), "hi4".to_string()]);
188
189 let name = Name::from_utf8(format!("{pubkey}.")).anyerr()?;
191 let res = resolver.lookup_ipv4(name, DNS_TIMEOUT).await?;
192 let records = res.collect::<Vec<_>>();
193 assert_eq!(records, vec![Ipv4Addr::LOCALHOST]);
194
195 let name = Name::from_utf8(format!("foo.bar.baz.{pubkey}.")).anyerr()?;
197 let res = resolver.lookup_ipv6(name, DNS_TIMEOUT).await?;
198 let records = res.collect::<Vec<_>>();
199 assert_eq!(records, vec![Ipv6Addr::LOCALHOST]);
200
201 server.shutdown().await?;
202 Ok(())
203 }
204
205 #[tokio::test]
206 #[traced_test]
207 async fn integration_smoke() -> Result {
208 let dir = tempfile::tempdir()?;
209 let server = Server::spawn_for_tests(dir.path()).await?;
210
211 let pkarr_relay = {
212 let mut url = server.http_url().expect("http is bound");
213 url.set_path("/pkarr");
214 url
215 };
216
217 let origin = "irohdns.example.";
218
219 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
220
221 let secret_key = SecretKey::from_bytes(&rng.random());
222 let endpoint_id = secret_key.public();
223 let tls_config = CaTlsConfig::default()
224 .client_config(default_provider())
225 .expect("infallible");
226 let pkarr = PkarrRelayClient::new(pkarr_relay, tls_config, DnsResolver::default());
227 let relay_url: RelayUrl = "https://relay.example.".parse()?;
228 let endpoint_info = EndpointInfo::new(endpoint_id).with_relay_url(relay_url.clone());
229 let signed_packet = endpoint_info.to_pkarr_signed_packet(&secret_key, 30)?;
230
231 pkarr.publish(&signed_packet).await?;
232
233 let resolver = test_resolver(server.dns_addr());
234 let res = resolver.lookup_endpoint_by_id(&endpoint_id, origin).await?;
235
236 assert_eq!(res.endpoint_id, endpoint_id);
237 assert_eq!(res.relay_urls().next(), Some(&relay_url));
238
239 server.shutdown().await?;
240 Ok(())
241 }
242
243 #[tokio::test]
244 #[traced_test]
245 async fn store_eviction() -> Result {
246 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
247
248 let options = Options {
249 eviction: Duration::from_millis(100),
250 eviction_interval: Duration::from_millis(100),
251 max_batch_time: Duration::from_millis(100),
252 ..Default::default()
253 };
254 let store = ZoneStore::in_memory(options, Default::default())?;
255
256 let signed_packet = random_signed_packet(&mut rng)?;
258 let key = PublicKeyBytes::from_signed_packet(&signed_packet);
259
260 store
261 .insert(signed_packet, PacketSource::PkarrPublish)
262 .await?;
263
264 tokio::time::sleep(Duration::from_secs(1)).await;
265 for _ in 0..10 {
266 let entry = store.get_signed_packet(&key).await?;
267 if entry.is_none() {
268 return Ok(());
269 }
270 tokio::time::sleep(Duration::from_secs(1)).await;
271 }
272 panic!("store did not evict packet");
273 }
274
275 #[tokio::test]
276 #[traced_test]
277 #[ignore = "flaky"]
278 async fn integration_mainline() -> Result {
279 let dir = tempfile::tempdir()?;
280 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
281
282 let testnet = Testnet::new_async(5).await.anyerr()?;
284 let bootstrap = testnet.bootstrap.clone();
285
286 let server = Server::spawn_for_tests_with_options(
288 dir.path(),
289 Some(BootstrapOption::Custom(bootstrap.clone())),
290 None,
291 None,
292 )
293 .await?;
294
295 let origin = "irohdns.example.";
296
297 let secret_key = SecretKey::from_bytes(&rng.random());
299 let endpoint_id = secret_key.public();
300 let relay_url: RelayUrl = "https://relay.example.".parse()?;
301 let endpoint_info = EndpointInfo::new(endpoint_id).with_relay_url(relay_url.clone());
302 let signed_packet = endpoint_info.to_pkarr_signed_packet(&secret_key, 30)?;
303
304 let mut dht_builder = DhtBuilder::default();
306 dht_builder.bootstrap(&bootstrap);
307 let dht = dht_builder.build().anyerr()?;
308 let item = MutableItem::new_signed_unchecked(
309 *secret_key.public().as_bytes(),
310 signed_packet.signature().to_bytes(),
311 signed_packet.encoded_packet(),
312 signed_packet.timestamp().as_micros() as i64,
313 None,
314 );
315 dht.clone()
316 .as_async()
317 .put_mutable(item, None)
318 .await
319 .anyerr()?;
320
321 let resolver = test_resolver(server.dns_addr());
323 let res = resolver.lookup_endpoint_by_id(&endpoint_id, origin).await?;
324
325 assert_eq!(res.endpoint_id, endpoint_id);
326 assert_eq!(res.relay_urls().next(), Some(&relay_url));
327
328 server.shutdown().await?;
329 Ok(())
330 }
331
332 fn test_resolver(nameserver: SocketAddr) -> DnsResolver {
333 DnsResolver::with_nameserver(nameserver)
334 }
335
336 fn random_signed_packet<R: CryptoRng + ?Sized>(rng: &mut R) -> Result<SignedPacket> {
337 let secret_key = SecretKey::from_bytes(&rng.random());
338 let endpoint_id = secret_key.public();
339 let relay_url: RelayUrl = "https://relay.example.".parse()?;
340 let endpoint_info = EndpointInfo::new(endpoint_id).with_relay_url(relay_url.clone());
341 let packet = endpoint_info.to_pkarr_signed_packet(&secret_key, 30)?;
342 Ok(packet)
343 }
344}