1use std::{
4 net::{Ipv4Addr, Ipv6Addr},
5 str::FromStr,
6};
7
8use {
9 hickory_client::{
10 client::{AsyncClient, ClientConnection, ClientHandle},
11 rr::{DNSClass, Name, RecordType},
12 udp::UdpClientConnection,
13 },
14 icann_rdap_client::{
15 http::{create_client, create_client_with_addr, ClientConfig},
16 iana::{qtype_to_bootstrap_url, BootstrapStore},
17 rdap::{rdap_url_request, QueryType},
18 RdapClientError,
19 },
20 icann_rdap_common::response::{get_related_links, ExtensionId},
21 reqwest::{header::HeaderValue, Url},
22 thiserror::Error,
23 tracing::{debug, info},
24 url::ParseError,
25};
26
27use crate::rt::results::{RunFeature, TestRun};
28
29use super::results::{DnsData, TestResults};
30
31#[derive(Default)]
32pub struct TestOptions {
33 pub skip_v4: bool,
34 pub skip_v6: bool,
35 pub skip_origin: bool,
36 pub origin_value: String,
37 pub chase_referral: bool,
38 pub expect_extensions: Vec<String>,
39 pub expect_groups: Vec<ExtensionGroup>,
40 pub allow_unregistered_extensions: bool,
41 pub one_addr: bool,
42 pub dns_resolver: Option<String>,
43}
44
45#[derive(Clone)]
46pub enum ExtensionGroup {
47 Gtld,
48 Nro,
49 NroAsn,
50}
51
52#[derive(Debug, Error)]
53pub enum TestExecutionError {
54 #[error(transparent)]
55 RdapClient(#[from] RdapClientError),
56 #[error(transparent)]
57 UrlParseError(#[from] ParseError),
58 #[error(transparent)]
59 AddrParseError(#[from] std::net::AddrParseError),
60 #[error("No host to resolve")]
61 NoHostToResolve,
62 #[error("No rdata")]
63 NoRdata,
64 #[error("Bad rdata")]
65 BadRdata,
66 #[error(transparent)]
67 Client(#[from] reqwest::Error),
68 #[error(transparent)]
69 InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),
70 #[error("Unsupporte Query Type")]
71 UnsupportedQueryType,
72 #[error("No referral to chase")]
73 NoReferralToChase,
74 #[error("Unregistered extension")]
75 UnregisteredExtension,
76}
77
78pub async fn execute_tests<BS: BootstrapStore>(
79 bs: &BS,
80 value: &QueryType,
81 options: &TestOptions,
82 client_config: &ClientConfig,
83) -> Result<TestResults, TestExecutionError> {
84 let bs_client = create_client(client_config)?;
85
86 let extensions = normalize_extension_ids(options)?;
88 let options = &TestOptions {
89 expect_extensions: extensions,
90 expect_groups: options.expect_groups.clone(),
91 origin_value: options.origin_value.clone(),
92 dns_resolver: options.dns_resolver.clone(),
93 ..*options
94 };
95
96 let mut query_url = match value {
98 QueryType::Help => return Err(TestExecutionError::UnsupportedQueryType),
99 QueryType::Url(url) => url.to_owned(),
100 _ => {
101 let base_url = qtype_to_bootstrap_url(&bs_client, bs, value, |reg| {
102 info!("Fetching IANA registry {} for value {value}", reg.url())
103 })
104 .await?;
105 value.query_url(&base_url)?
106 }
107 };
108 if options.chase_referral {
110 let client = create_client(client_config)?;
111 info!("Fetching referral from {query_url}");
112 let response_data = rdap_url_request(&query_url, &client).await?;
113 query_url = get_related_links(&response_data.rdap)
114 .first()
115 .ok_or(TestExecutionError::NoReferralToChase)?
116 .to_string();
117 info!("Referral is {query_url}");
118 }
119
120 let parsed_url = Url::parse(&query_url)?;
121 let port = parsed_url.port().unwrap_or_else(|| {
122 if parsed_url.scheme().eq("https") {
123 443
124 } else {
125 80
126 }
127 });
128 let host = parsed_url
129 .host_str()
130 .ok_or(TestExecutionError::NoHostToResolve)?;
131
132 info!("Testing {query_url}");
133 let dns_data = get_dns_records(host, options).await?;
134 let mut test_results = TestResults::new(query_url.clone(), dns_data.clone());
135
136 let mut more_runs = true;
137 for v4 in dns_data.v4_addrs {
138 let mut test_run = TestRun::new_v4(vec![], v4, port);
140 if !options.skip_v4 && more_runs {
141 let client = create_client_with_addr(client_config, host, test_run.socket_addr)?;
142 info!("Sending request to {}", test_run.socket_addr);
143 let rdap_response = rdap_url_request(&query_url, &client).await;
144 test_run = test_run.end(rdap_response, options);
145 }
146 test_results.add_test_run(test_run);
147
148 let mut test_run = TestRun::new_v4(vec![RunFeature::OriginHeader], v4, port);
150 if !options.skip_v4 && !options.skip_origin && more_runs {
151 let client_config = ClientConfig::from_config(client_config)
152 .origin(HeaderValue::from_str(&options.origin_value)?)
153 .build();
154 let client = create_client_with_addr(&client_config, host, test_run.socket_addr)?;
155 info!("Sending request to {}", test_run.socket_addr);
156 let rdap_response = rdap_url_request(&query_url, &client).await;
157 test_run = test_run.end(rdap_response, options);
158 }
159 test_results.add_test_run(test_run);
160 if options.one_addr {
161 more_runs = false;
162 }
163 }
164
165 let mut more_runs = true;
166 for v6 in dns_data.v6_addrs {
167 let mut test_run = TestRun::new_v6(vec![], v6, port);
169 if !options.skip_v6 && more_runs {
170 let client = create_client_with_addr(client_config, host, test_run.socket_addr)?;
171 info!("Sending request to {}", test_run.socket_addr);
172 let rdap_response = rdap_url_request(&query_url, &client).await;
173 test_run = test_run.end(rdap_response, options);
174 }
175 test_results.add_test_run(test_run);
176
177 let mut test_run = TestRun::new_v6(vec![RunFeature::OriginHeader], v6, port);
179 if !options.skip_v6 && !options.skip_origin && more_runs {
180 let client_config = ClientConfig::from_config(client_config)
181 .origin(HeaderValue::from_str(&options.origin_value)?)
182 .build();
183 let client = create_client_with_addr(&client_config, host, test_run.socket_addr)?;
184 info!("Sending request to {}", test_run.socket_addr);
185 let rdap_response = rdap_url_request(&query_url, &client).await;
186 test_run = test_run.end(rdap_response, options);
187 }
188 test_results.add_test_run(test_run);
189 if options.one_addr {
190 more_runs = false;
191 }
192 }
193
194 test_results.end(options);
195 info!("Testing complete.");
196 Ok(test_results)
197}
198
199async fn get_dns_records(host: &str, options: &TestOptions) -> Result<DnsData, TestExecutionError> {
200 if let Ok(ip4) = Ipv4Addr::from_str(host) {
202 return Ok(DnsData {
203 v4_cname: None,
204 v6_cname: None,
205 v4_addrs: vec![ip4],
206 v6_addrs: vec![],
207 });
208 } else if let Ok(ip6) = Ipv6Addr::from_str(host.trim_start_matches('[').trim_end_matches(']')) {
209 return Ok(DnsData {
210 v4_cname: None,
211 v6_cname: None,
212 v4_addrs: vec![],
213 v6_addrs: vec![ip6],
214 });
215 }
216
217 let def_dns_resolver = "8.8.8.8:53".to_string();
218 let dns_resolver = options.dns_resolver.as_ref().unwrap_or(&def_dns_resolver);
219 let conn = UdpClientConnection::new(dns_resolver.parse()?)
220 .unwrap()
221 .new_stream(None);
222 let (mut client, bg) = AsyncClient::connect(conn).await.unwrap();
223
224 tokio::spawn(bg);
226
227 let mut dns_data = DnsData::default();
228
229 let query = client.query(Name::from_str(host).unwrap(), DNSClass::IN, RecordType::A);
231
232 let response = query.await.unwrap();
234
235 for answer in response.answers() {
236 match answer.record_type() {
237 RecordType::CNAME => {
238 let cname = answer
239 .data()
240 .ok_or(TestExecutionError::NoRdata)?
241 .clone()
242 .into_cname()
243 .map_err(|_e| TestExecutionError::BadRdata)?
244 .0
245 .to_string();
246 debug!("Found cname {cname}");
247 dns_data.v4_cname = Some(cname);
248 }
249 RecordType::A => {
250 let addr = answer
251 .data()
252 .ok_or(TestExecutionError::NoRdata)?
253 .clone()
254 .into_a()
255 .map_err(|_e| TestExecutionError::BadRdata)?
256 .0;
257 debug!("Found IPv4 {addr}");
258 dns_data.v4_addrs.push(addr);
259 }
260 _ => {
261 }
263 };
264 }
265
266 let query = client.query(
268 Name::from_str(host).unwrap(),
269 DNSClass::IN,
270 RecordType::AAAA,
271 );
272
273 let response = query.await.unwrap();
275
276 for answer in response.answers() {
277 match answer.record_type() {
278 RecordType::CNAME => {
279 let cname = answer
280 .data()
281 .ok_or(TestExecutionError::NoRdata)?
282 .clone()
283 .into_cname()
284 .map_err(|_e| TestExecutionError::BadRdata)?
285 .0
286 .to_string();
287 debug!("Found cname {cname}");
288 dns_data.v6_cname = Some(cname);
289 }
290 RecordType::AAAA => {
291 let addr = answer
292 .data()
293 .ok_or(TestExecutionError::NoRdata)?
294 .clone()
295 .into_aaaa()
296 .map_err(|_e| TestExecutionError::BadRdata)?
297 .0;
298 debug!("Found IPv6 {addr}");
299 dns_data.v6_addrs.push(addr);
300 }
301 _ => {
302 }
304 };
305 }
306
307 Ok(dns_data)
308}
309
310fn normalize_extension_ids(options: &TestOptions) -> Result<Vec<String>, TestExecutionError> {
311 let mut retval = options.expect_extensions.clone();
312
313 if !options.allow_unregistered_extensions {
315 for ext in &retval {
316 if ExtensionId::from_str(ext).is_err() {
317 return Err(TestExecutionError::UnregisteredExtension);
318 }
319 }
320 }
321
322 for group in &options.expect_groups {
324 match group {
325 ExtensionGroup::Gtld => {
326 retval.push(format!(
327 "{}|{}",
328 ExtensionId::IcannRdapResponseProfile0,
329 ExtensionId::IcannRdapResponseProfile1
330 ));
331 retval.push(format!(
332 "{}|{}",
333 ExtensionId::IcannRdapTechnicalImplementationGuide0,
334 ExtensionId::IcannRdapTechnicalImplementationGuide1
335 ));
336 }
337 ExtensionGroup::Nro => {
338 retval.push(ExtensionId::NroRdapProfile0.to_string());
339 retval.push(ExtensionId::Cidr0.to_string());
340 }
341 ExtensionGroup::NroAsn => {
342 retval.push(ExtensionId::NroRdapProfile0.to_string());
343 retval.push(format!(
344 "{}|{}",
345 ExtensionId::NroRdapProfileAsnFlat0,
346 ExtensionId::NroRdapProfileAsnHierarchical0
347 ));
348 }
349 }
350 }
351 Ok(retval)
352}
353
354#[cfg(test)]
355#[allow(non_snake_case)]
356mod tests {
357 use icann_rdap_common::response::ExtensionId;
358
359 use crate::rt::exec::{ExtensionGroup, TestOptions};
360
361 use super::normalize_extension_ids;
362
363 #[test]
364 fn GIVEN_gtld_WHEN_normalize_extensions_THEN_list_contains_gtld_ids() {
365 let given = vec![ExtensionGroup::Gtld];
367
368 let options = TestOptions {
370 expect_groups: given,
371 ..Default::default()
372 };
373 let actual = normalize_extension_ids(&options).unwrap();
374
375 let expected1 = format!(
377 "{}|{}",
378 ExtensionId::IcannRdapResponseProfile0,
379 ExtensionId::IcannRdapResponseProfile1
380 );
381 assert!(actual.contains(&expected1));
382
383 let expected2 = format!(
384 "{}|{}",
385 ExtensionId::IcannRdapTechnicalImplementationGuide0,
386 ExtensionId::IcannRdapTechnicalImplementationGuide1
387 );
388 assert!(actual.contains(&expected2));
389 }
390
391 #[test]
392 fn GIVEN_nro_and_foo_WHEN_normalize_extensions_THEN_list_contains_nro_ids_and_foo() {
393 let groups = vec![ExtensionGroup::Nro];
395 let exts = vec!["foo1".to_string()];
396
397 let options = TestOptions {
399 allow_unregistered_extensions: true,
400 expect_extensions: exts,
401 expect_groups: groups,
402 ..Default::default()
403 };
404 let actual = normalize_extension_ids(&options).unwrap();
405 dbg!(&actual);
406
407 assert!(actual.contains(&ExtensionId::NroRdapProfile0.to_string()));
409 assert!(actual.contains(&ExtensionId::Cidr0.to_string()));
410 assert!(actual.contains(&"foo1".to_string()));
411 }
412
413 #[test]
414 fn GIVEN_nro_and_foo_WHEN_unreg_disallowed_THEN_err() {
415 let groups = vec![ExtensionGroup::Nro];
417 let exts = vec!["foo1".to_string()];
418
419 let options = TestOptions {
421 expect_extensions: exts,
422 expect_groups: groups,
423 ..Default::default()
424 };
425 let actual = normalize_extension_ids(&options);
426
427 assert!(actual.is_err())
429 }
430
431 #[test]
432 fn GIVEN_unregistered_ext_WHEN_normalize_extensions_THEN_error() {
433 let given = vec!["foo".to_string()];
435
436 let options = TestOptions {
438 expect_extensions: given,
439 ..Default::default()
440 };
441 let actual = normalize_extension_ids(&options);
442
443 assert!(actual.is_err());
445 }
446
447 #[test]
448 fn GIVEN_unregistered_ext_WHEN_allowed_THEN_no_error() {
449 let given = vec!["foo".to_string()];
451
452 let options = TestOptions {
454 expect_extensions: given,
455 allow_unregistered_extensions: true,
456 ..Default::default()
457 };
458 let actual = normalize_extension_ids(&options);
459
460 assert!(actual.is_ok());
462 }
463}