Rust: Networks of Nameservers

Because both DNS registries and IP address registries use RDAP, writing a custom client to look for the networks where a domain name’s nameservers are located is easy to accomplish.

This example uses Rust, and is a bit longer than the previous examples because it loops over a list of domains to find their IP addresses, then loops over those IP addresses to get the network information.

The complete source code may be found here, but the meat of the code is below:

    for domain_name in domains_names {
        println!("domain: {domain_name}");
        let query = QueryType::from_str(&domain_name)?;
        let response = rdap_bootstrapped_request(&query, &client, &store, |_| {}).await?;
        let RdapResponse::Domain(domain) = response.rdap else {
            panic!("response is not a domain")
        };
        let name_servers = domain.nameservers.unwrap_or_default();
        for ns in name_servers {
            let ns_name = ns.ldh_name.clone().unwrap_or(
                ns.unicode_name
                    .clone()
                    .unwrap_or("No Nameserver Name Given".to_string()),
            );
            if let Some(ip_addresses) = ns.ip_addresses.as_ref() {
                let all_ips = vec![
                    ip_addresses.v6.clone().unwrap_or_default(),
                    ip_addresses.v4.clone().unwrap_or_default(),
                ]
                .into_iter()
                .flatten()
                .collect::<Vec<String>>();
                for ip in all_ips {
                    let ip_query = QueryType::from_str(&ip).unwrap();
                    let RdapResponse::Network(ip_response) =
                        rdap_bootstrapped_request(&ip_query, &client, &store, |_| {})
                            .await
                            .unwrap()
                            .rdap
                    else {
                        panic!("response is not IP network")
                    };
                    let start_ip = ip_response
                        .start_address
                        .unwrap_or("NO START IP".to_string());
                    let end_ip = ip_response.end_address.unwrap_or("NO END IP".to_string());
                    println!("{ns_name}({ip}) is in network {start_ip} - {end_ip}");
                }
            }
        }
    }  

This code uses the ICANN RDAP Client Library, and consists of three nested loops. Each loop uses the same Client and uses the built-in bootstrapping mechanism. The outermost loop iterates over the domain names querying for each, the next loop gets the IP addresses of the nameservers returned in those domain name lookups, and the innermost loop then queries for the IP networks of the IP addresses of the nameservers.

When it is run, it takes domain names as command line arguments. Here is the sample output:

$ cargo run -- iana.org icann.org
   Compiling rust_domain_ip v0.1.0 (/home/andy/projects/rdap_guide/src/examples/clients/rust_domain_ip)
    Finished dev [unoptimized + debuginfo] target(s) in 2.64s
     Running `target/debug/rust_domain_ip iana.org icann.org`
domain: iana.org
ns.icann.org(2001:500:89::53) is in network 2001:500:89:: - 2001:500:89:ffff:ffff:ffff:ffff:ffff
ns.icann.org(199.4.138.53) is in network 199.4.138.0 - 199.4.138.255
domain: icann.org
ns.icann.org(2001:500:89::53) is in network 2001:500:89:: - 2001:500:89:ffff:ffff:ffff:ffff:ffff
ns.icann.org(199.4.138.53) is in network 199.4.138.0 - 199.4.138.255