use std::net::{IpAddr};
use lazy_static::lazy_static;
use rand::{thread_rng, Rng};
use trust_dns_resolver::Resolver;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts, NameServerConfigGroup};
use curl::easy::{Easy2, Handler, WriteError};
#[derive(Debug)]
pub enum IpResolveError {
UnResolvableError { message: String }
}
struct Collector(Vec<u8>);
impl Handler for Collector {
fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {
self.0.extend_from_slice(data);
Ok(data.len())
}
}
static OPENDNS_MYIP: &str = "myip.opendns.com.";
lazy_static! {
static ref OPENDNS_RESOLVER: Resolver = {
let ips: Vec<IpAddr> = vec![
"208.67.222.222",
"208.67.220.220",
"208.67.222.220",
"208.67.220.222",
"2620:0:ccc::2",
"2620:0:ccd::2"
]
.iter()
.map(|addr_str| { addr_str.parse::<IpAddr>().unwrap() })
.collect();
let config = ResolverConfig::from_parts(
None,
vec![],
NameServerConfigGroup::from_ips_clear(&ips, 53)
);
Resolver::new(
config,
ResolverOpts::default()
).unwrap()
};
static ref HTTP_ENDPOINTS: Vec<&'static str> = vec![
"https://diagnostic.opendns.com/myip",
"https://tnx.nl/ip",
"https://icanhazip.com/"
];
}
fn dns_query() -> Option<IpAddr> {
let response = OPENDNS_RESOLVER.lookup_ip(&OPENDNS_MYIP).unwrap();
response.iter().next()
}
fn http_query(endpoint: &str) -> Option<IpAddr> {
let mut easy = Easy2::new(Collector(Vec::new()));
easy.get(true).unwrap();
easy.url(endpoint).unwrap();
easy.perform().unwrap();
let response_code = easy.response_code().unwrap();
if response_code != 200 {
return None
}
let contents = easy.get_ref();
match String::from_utf8_lossy(&contents.0).trim().parse::<IpAddr>() {
Ok(addr) => Some(addr),
_ => None
}
}
pub fn resolve() -> Result<IpAddr, IpResolveError> {
let mut rng = thread_rng();
let mut endpoints = HTTP_ENDPOINTS.clone();
rng.shuffle(&mut endpoints);
for endpoint in endpoints {
match http_query(endpoint) {
Some(addr) => return Ok(addr),
None => continue
}
}
dns_query().ok_or(IpResolveError::UnResolvableError{ message: "Cannot resolve IP neither using HTTP nor DNS".to_owned() })
}
#[cfg(test)]
mod tests {
use super::resolve;
#[test]
fn test_resolve() {
let ip = resolve().unwrap();
eprintln!("{:?}", ip);
assert!(ip.is_ipv4() || ip.is_ipv6());
#[cfg(nightly)]
assert!(ip.is_global())
}
}