acme_validation_propagation/
lib.rs1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use hickory_resolver::{
4 config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
5 Resolver,
6};
7use std::{convert::identity, net::IpAddr, thread::sleep, time::Duration};
8
9use crate::error::Error;
10use resolver::ResolverType;
11
12mod error;
13mod resolver;
14
15pub type Result<T> = std::result::Result<T, Error>;
16
17const MAX_RETRIES: usize = 720;
18const WAIT_SECONDS: u64 = 5;
19
20fn ipv6_resolver(
21 group: NameServerConfigGroup,
22 recursion: bool,
23 ipv6_only: bool,
24) -> Result<Resolver> {
25 let config = ResolverConfig::from_parts(None, vec![], group);
26 let mut options = ResolverOpts::default();
27 if ipv6_only {
28 options.ip_strategy = LookupIpStrategy::Ipv6Only;
29 }
30 options.recursion_desired = recursion;
31 options.use_hosts_file = false;
32 Resolver::new(config, options).map_err(Error::from)
33}
34
35fn recursive_resolver(ips: &[IpAddr], ipv6_only: bool) -> Result<Resolver> {
36 let group = NameServerConfigGroup::from_ips_clear(ips, 53, false);
37 ipv6_resolver(group, true, ipv6_only)
38}
39
40pub fn wait<S>(domain_name: S, challenge: S) -> Result<()>
44where
45 S: AsRef<str>,
46{
47 let resolvers = ResolverType::Google
48 .recursive_resolver(false)
49 .and_then(|resolver| resolver.authoritive_resolvers(domain_name.as_ref()))?;
50
51 let mut i: usize = 0;
52
53 sleep(Duration::from_secs(1));
54 while !resolvers
55 .iter()
56 .map(|resolver| resolver.has_single_acme(domain_name.as_ref(), challenge.as_ref()))
57 .collect::<Result<Vec<_>>>()?
58 .into_iter()
59 .all(identity)
60 && i < MAX_RETRIES
61 {
62 i += 1;
63 tracing::warn!("Attempt {} failed", i);
64 sleep(Duration::from_secs(WAIT_SECONDS));
65 }
66 if i >= MAX_RETRIES {
67 tracing::error!("Timeout checking acme challenge record");
68 Err(Error::AcmeChallege)
69 } else {
70 Ok(())
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use std::{fmt::Display, net::IpAddr};
77
78 use hickory_resolver::{
79 lookup::{Ipv6Lookup, NsLookup},
80 proto::rr::rdata::{AAAA, NS},
81 Resolver,
82 };
83
84 use crate::{error::Error, ResolverType};
85
86 fn to_string<D: Display>(d: D) -> String {
87 d.to_string()
88 }
89
90 fn aaaa_to_ipv6(aaaa: AAAA) -> IpAddr {
91 IpAddr::V6(*aaaa)
92 }
93
94 fn lookup(name: &str) -> impl Fn(Resolver) -> Result<Ipv6Lookup, Error> + '_ {
95 move |resolver| resolver.ipv6_lookup(name).map_err(Error::from)
96 }
97
98 fn ns_lookup(name: &str) -> impl Fn(Resolver) -> Result<NsLookup, Error> + '_ {
99 move |resolver| resolver.ns_lookup(name).map_err(Error::from)
100 }
101
102 fn aaaa_mapper(f: fn(AAAA) -> IpAddr) -> impl Fn(Ipv6Lookup) -> Vec<IpAddr> {
103 move |lookup| lookup.into_iter().map(f).collect()
104 }
105
106 fn ns_mapper(f: fn(NS) -> String) -> impl Fn(NsLookup) -> Vec<String> {
107 move |lookup| lookup.into_iter().map(f).collect()
108 }
109
110 fn ipv6_address_lookup(name: &str) -> Result<Vec<IpAddr>, Error> {
111 ResolverType::Google
112 .resolver(true)
113 .and_then(lookup(name))
114 .map(aaaa_mapper(aaaa_to_ipv6))
115 }
116
117 fn nameservers_lookup(name: &str) -> Result<Vec<String>, Error> {
118 ResolverType::Google
119 .resolver(true)
120 .and_then(ns_lookup(name))
121 .map(ns_mapper(to_string))
122 }
123
124 #[test]
125 fn test_www_paulmin_nl() {
126 let addresses = ipv6_address_lookup("www.paulmin.nl.").unwrap();
127 assert!(addresses.contains(&"2606:50c0:8000::153".parse::<IpAddr>().unwrap()),);
128 assert!(addresses.contains(&"2606:50c0:8001::153".parse::<IpAddr>().unwrap()),);
129 assert!(addresses.contains(&"2606:50c0:8002::153".parse::<IpAddr>().unwrap()),);
130 assert!(addresses.contains(&"2606:50c0:8003::153".parse::<IpAddr>().unwrap()),);
131 }
132
133 #[test]
134 fn test_ns0_transip_net() {
135 assert_eq!(
136 ipv6_address_lookup("ns0.transip.net").unwrap(),
137 vec!["2a01:7c8:dddd:195::195".parse::<IpAddr>().unwrap(),],
138 );
139 }
140
141 #[test]
142 fn test_ns1_transip_nl() {
143 assert_eq!(
144 ipv6_address_lookup("ns1.transip.nl.").unwrap(),
145 vec!["2a01:7c8:7000:195::195".parse::<IpAddr>().unwrap(),],
146 );
147 }
148
149 #[test]
150 fn test_ns2_transip_eu() {
151 assert_eq!(
152 ipv6_address_lookup("ns2.transip.eu.").unwrap(),
153 vec!["2a01:7c8:f:c1f::195".parse::<IpAddr>().unwrap(),],
154 );
155 }
156
157 #[test]
158 fn test_domain_ns() {
159 let mut domain = nameservers_lookup("paulmin.nl").unwrap();
160 domain.sort();
161 assert_eq!(
162 domain,
163 vec![
164 "ns0.transip.net.".to_owned(),
165 "ns1.transip.nl.".to_owned(),
166 "ns2.transip.eu.".to_owned(),
167 ],
168 );
169 }
170}