use crate::{Error, IpType, Provider, Result};
use async_trait::async_trait;
use derive_deref::Deref;
use hickory_resolver::{
config::{NameServerConfig, ResolverConfig}, name_server::TokioConnectionProvider, proto::xfer::Protocol, ResolveError, TokioResolver
};
use log::{debug, trace};
use reqwest::{Client, Proxy};
use serde::Deserialize;
use serde_json::Value;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error as ErrorDerive;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum ProviderMethod {
#[serde(rename = "plain")]
Plain,
#[serde(rename = "json")]
Json,
#[serde(rename = "dns")]
Dns,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct ProviderInfo {
name: String,
#[serde(rename = "type")]
addr_type: IpType,
method: ProviderMethod,
url: String,
key: Option<String>,
}
impl Default for ProviderInfo {
fn default() -> Self {
Self {
name: String::default(),
addr_type: IpType::Ipv4,
method: ProviderMethod::Plain,
url: String::default(),
key: None,
}
}
}
#[derive(Debug, ErrorDerive)]
pub enum GlobalIpError {
#[error(transparent)]
ReqwestError(#[from] reqwest::Error),
#[error(transparent)]
JsonParseError(#[from] serde_json::Error),
#[error("field `{0}' does not exist in response")]
JsonNotFoundError(String),
#[error("field `{0}' in response can't be decoded")]
JsonDecodeError(String),
#[error(transparent)]
DnsError(#[from] Box<ResolveError>),
#[error("specified DNS server `{0}' has no address")]
DnsNoServerError(String),
}
macro_rules! make_get_type {
() => {
fn get_type(&self) -> IpType {
self.info.addr_type
}
};
}
macro_rules! make_new {
($name: ident) => {
impl $name {
const fn new(info: ProviderInfo, timeout: u64, proxy: Option<String>) -> Self {
Self(AbstractProvider {
info,
timeout,
proxy,
})
}
}
};
}
#[derive(Clone, Debug)]
pub struct AbstractProvider {
pub info: ProviderInfo,
pub timeout: u64,
pub proxy: Option<String>,
}
impl Default for AbstractProvider {
fn default() -> Self {
Self {
info: ProviderInfo::default(),
timeout: 1000,
proxy: None,
}
}
}
fn build_client(timeout: u64, proxy: Option<&str>) -> reqwest::Result<Client> {
let client = match (timeout, proxy) {
(0, None) => Client::new(),
(0, Some(proxy)) => Client::builder().proxy(Proxy::all(proxy)?).build()?,
(_, None) => Client::builder()
.timeout(Duration::from_millis(timeout))
.build()?,
(_, Some(proxy)) => Client::builder()
.proxy(Proxy::all(proxy)?)
.timeout(Duration::from_millis(timeout))
.build()?,
};
Ok(client)
}
async fn build_client_get(url: &str, timeout: u64, proxy: Option<&str>) -> Result<String> {
Ok((async {
let client = build_client(timeout, proxy)?;
debug!("Reqwesting {url:?} through proxy {proxy:?}");
client
.get(url)
.send()
.await?
.error_for_status()?
.text()
.await
})
.await
.map_err(GlobalIpError::ReqwestError)?)
}
fn create_ipaddr(addr: &str, addr_type: IpType) -> Result<IpAddr> {
Ok(match addr_type {
IpType::Ipv4 => IpAddr::V4(Ipv4Addr::from_str(addr).map_err(Error::AddrParseError)?),
IpType::Ipv6 => IpAddr::V6(Ipv6Addr::from_str(addr).map_err(Error::AddrParseError)?),
})
}
#[derive(Clone, Debug, Deref)]
pub struct ProviderPlain(AbstractProvider);
make_new! {ProviderPlain}
#[async_trait]
impl Provider for ProviderPlain {
async fn get_addr(&self) -> Result<IpAddr> {
let addr = build_client_get(&self.info.url, self.timeout, self.proxy.as_deref()).await?;
debug!("Plain provider {:?} returned {:?}", self.info, addr);
create_ipaddr(&addr, self.info.addr_type)
}
make_get_type! {}
}
#[derive(Clone, Debug, Deref)]
pub struct ProviderJson(AbstractProvider);
make_new! {ProviderJson}
#[async_trait]
impl Provider for ProviderJson {
async fn get_addr(&self) -> Result<IpAddr> {
let resp = build_client_get(&self.info.url, self.timeout, self.proxy.as_deref()).await?;
trace!("Provider got response {:?}", resp);
let json: Value = serde_json::from_str(&resp).map_err(GlobalIpError::JsonParseError)?;
let key = self
.info
.key
.clone()
.expect("`key' should exist for JSON providers");
let addr = json
.get(&key)
.ok_or_else(|| GlobalIpError::JsonNotFoundError(key.clone()))?
.as_str()
.ok_or(GlobalIpError::JsonDecodeError(key))?;
debug!("JSON provider {:?} returned {:?}", self.info, addr);
create_ipaddr(addr, self.info.addr_type)
}
make_get_type! {}
}
#[derive(Clone, Debug, Deref)]
pub struct ProviderDns(AbstractProvider);
make_new! {ProviderDns}
async fn host_to_addr(
resolver: TokioResolver,
host: &str,
addr_type: IpType,
) -> std::result::Result<Option<IpAddr>, ResolveError> {
Ok(match addr_type {
IpType::Ipv4 => {
let srv = resolver.ipv4_lookup(host).await?;
let record = srv.iter().next();
record.map(|r| IpAddr::V4(r.0))
}
IpType::Ipv6 => {
let srv = resolver.ipv6_lookup(host).await?;
let record = srv.iter().next();
record.map(|r| IpAddr::V6(r.0))
}
})
}
#[async_trait]
impl Provider for ProviderDns {
async fn get_addr(&self) -> Result<IpAddr> {
let (query, server) = self
.info
.url
.split_once('@')
.expect("DNS Provider URL should be like query@server");
let resolver = TokioResolver::builder_tokio()
.map_err(|e| GlobalIpError::DnsError(Box::new(e)))?
.build();
debug!("Resolving {server:?} on {resolver:?}");
let server_addr = host_to_addr(resolver, server, self.info.addr_type)
.await
.map_err(|e| GlobalIpError::DnsError(Box::new(e)))?
.ok_or_else(|| GlobalIpError::DnsNoServerError(server.to_string()))?;
let ns = NameServerConfig {
socket_addr: std::net::SocketAddr::new(server_addr, 53),
protocol: Protocol::Udp,
tls_dns_name: None,
trust_negative_responses: false,
http_endpoint: None,
bind_addr: None,
};
let mut config = ResolverConfig::new();
config.add_name_server(ns);
let mut resolver = TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
resolver.options_mut().timeout = Duration::from_millis(self.timeout);
let resolver = resolver.build();
debug!("Resolving {query:?} on {resolver:?}");
let addr = host_to_addr(resolver, query, self.info.addr_type)
.await
.map_err(|e| GlobalIpError::DnsError(Box::new(e)))?
.ok_or_else(|| GlobalIpError::DnsNoServerError(server.to_string()))?;
debug!("DNS provider {:?} returned {:?}", self.info, addr);
Ok(addr)
}
make_get_type! {}
}
#[derive(Debug)]
pub struct ProviderMultiple {
providers: Vec<ProviderInfo>,
addr_type: IpType,
timeout: u64,
proxy: Option<String>,
}
impl Default for ProviderMultiple {
fn default() -> Self {
let providers: Vec<ProviderInfo> = serde_json::from_str(DEFAULT_PROVIDERS).unwrap();
Self {
providers,
addr_type: IpType::Ipv4,
timeout: 1000,
proxy: None,
}
}
}
impl ProviderMultiple {
#[must_use]
pub fn default_v6() -> Self {
Self {
addr_type: IpType::Ipv6,
..Self::default()
}
}
pub fn from_json(json: &str) -> serde_json::Result<Self> {
let providers: Vec<ProviderInfo> = serde_json::from_str(json)?;
Ok(Self {
providers,
..Self::default()
})
}
}
#[async_trait]
impl Provider for ProviderMultiple {
async fn get_addr(&self) -> Result<IpAddr> {
let mut result: Result<IpAddr> = Err(Error::NoAddress);
trace!("Registered providers: {:?}", self.providers);
for info in &self.providers {
if info.addr_type != self.addr_type {
continue;
}
let proxy = self.proxy.clone();
let this_result = match info.method {
ProviderMethod::Plain => {
let provider = ProviderPlain::new(info.clone(), self.timeout, proxy);
provider.get_addr().await
}
ProviderMethod::Json => {
let provider = ProviderJson::new(info.clone(), self.timeout, proxy);
provider.get_addr().await
}
ProviderMethod::Dns => {
let provider = ProviderDns::new(info.clone(), self.timeout, proxy);
provider.get_addr().await
}
};
if this_result.is_ok() {
debug!("Using result {this_result:?} from provider {info:?}");
result = this_result;
break;
}
}
result
}
fn get_type(&self) -> IpType {
self.addr_type
}
}
pub const DEFAULT_PROVIDERS: &str = r#"[
{
"method": "plain",
"name": "ipify",
"type": "IPv4",
"url": "https://api.ipify.org/"
},
{
"method": "plain",
"name": "ipify",
"type": "IPv6",
"url": "https://api6.ipify.org/"
},
{
"method": "plain",
"name": "ipv6-test",
"type": "IPv4",
"url": "https://v4.ipv6-test.com/api/myip.php"
},
{
"method": "plain",
"name": "ipv6-test",
"type": "IPv6",
"url": "https://v6.ipv6-test.com/api/myip.php"
},
{
"method": "plain",
"name": "ident.me",
"type": "IPv4",
"url": "https://v4.ident.me/"
},
{
"method": "plain",
"name": "ident.me",
"type": "IPv6",
"url": "https://v6.ident.me/"
},
{
"key": "ip",
"method": "json",
"name": "test-ipv6",
"padding": "callback",
"type": "IPv4",
"url": "https://ipv4.test-ipv6.com/ip/"
},
{
"key": "ip",
"method": "json",
"name": "test-ipv6",
"padding": "callback",
"type": "IPv6",
"url": "https://ipv6.test-ipv6.com/ip/"
},
{
"method": "dns",
"name": "opendns.com",
"type": "IPv4",
"url": "myip.opendns.com@resolver1.opendns.com"
},
{
"method": "dns",
"name": "opendns.com",
"type": "IPv6",
"url": "myip.opendns.com@resolver1.opendns.com"
},
{
"method": "dns",
"name": "akamai.com",
"type": "IPv4",
"url": "whoami.akamai.com@ns1-1.akamaitech.net"
},
{
"method": "plain",
"name": "akamai.com",
"type": "IPv4",
"url": "http://whatismyip.akamai.com"
},
{
"method": "plain",
"name": "akamai.com",
"type": "IPv6",
"url": "http://ipv6.whatismyip.akamai.com"
}
]"#;