#![warn(
clippy::pedantic,
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_op_in_unsafe_fn,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
variant_size_differences
)]
#![allow(clippy::no_effect_underscore_binding)]
pub mod gip;
pub mod hostip;
pub mod libc_getips;
use async_trait::async_trait;
use serde::Deserialize;
use std::net::IpAddr;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum IpScope {
Global,
Local,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum IpType {
#[serde(rename = "IPv4")]
Ipv4,
#[serde(rename = "IPv6")]
Ipv6,
}
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
GlobalIpError(#[from] gip::GlobalIpError),
#[error(transparent)]
AddrParseError(#[from] std::net::AddrParseError),
#[error(transparent)]
UnicodeParseError(#[from] std::string::FromUtf8Error),
#[error("Command exited with status {0}")]
NonZeroExit(std::process::ExitStatus),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error("no address returned")]
NoAddress,
}
pub type Result<R> = std::result::Result<R, Error>;
#[async_trait]
pub trait Provider: Sized {
async fn get_addr(&self) -> Result<IpAddr>;
fn get_type(&self) -> IpType;
}
pub async fn get_ip(ip_type: IpType, ip_scope: IpScope, nic: Option<&str>) -> Result<IpAddr> {
match (ip_type, ip_scope) {
(IpType::Ipv4, IpScope::Global) => {
let p = gip::ProviderMultiple::default();
p.get_addr().await
}
(IpType::Ipv6, IpScope::Global) => {
let p = gip::ProviderMultiple::default_v6();
p.get_addr().await
}
(IpType::Ipv4, IpScope::Local) => {
let p = hostip::LocalLibcProvider::new(nic, IpType::Ipv4);
p.get_addr().await
}
(IpType::Ipv6, IpScope::Local) => {
if let Some(nic) = nic {
let command_provider = hostip::LocalIpv6CommandProvider::new(nic, true);
let command_result = command_provider.get_addr().await;
if command_result.is_ok() || matches!(command_result, Err(Error::NoAddress)) {
return command_result;
}
}
let p = hostip::LocalLibcProvider::new(nic, IpType::Ipv6);
p.get_addr().await
}
}
}
#[cfg(test)]
mod test {
use crate::{get_ip, gip::ProviderMultiple, libc_getips, IpScope, IpType, Provider};
use std::net::IpAddr;
fn has_any_ipv6_address(iface_name: Option<&str>, global: bool) -> bool {
libc_getips::get_iface_addrs(Some(IpType::Ipv6), iface_name).is_ok_and(|addresses| {
addresses.iter().any(|ip| {
!ip.is_loopback()
&& !ip.is_unspecified()
&& if global {
if let IpAddr::V6(dcasted) = ip {
(dcasted.segments()[0] & 0xffc0) != 0xfe80
&& (dcasted.segments()[0] & 0xfe00) != 0xfc00
} else {
unreachable!()
}
} else {
true
}
})
})
}
#[tokio::test]
async fn test_global_ipv4_is_any() {
let addr = get_ip(IpType::Ipv4, IpScope::Global, None).await;
assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
if let IpAddr::V4(addr) = addr.unwrap() {
assert!(
!addr.is_private(),
"The result of get_addr() should not be private"
);
assert!(
!addr.is_loopback(),
"The result of get_addr() should not be loopback"
);
} else {
panic!("The result of get_addr() should be an IPv4 address");
}
}
#[tokio::test]
async fn test_global_ipv4_just_dns_is_any() {
const DNS_PROVIDERS: &str = r#"[
{
"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"
}
]"#;
let provider = ProviderMultiple::from_json(DNS_PROVIDERS).unwrap();
let addr = provider.get_addr().await;
assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
}
#[tokio::test]
async fn test_global_ipv6_is_any() {
if !has_any_ipv6_address(None, true) {
return;
}
let addr = get_ip(IpType::Ipv6, IpScope::Global, None).await;
assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
if let IpAddr::V6(addr) = addr.unwrap() {
assert!(
!addr.is_loopback(),
"The result of get_addr() should not be loopback"
);
assert!(
!addr.is_unspecified(),
"The result of get_addr() should not be unspecified"
);
} else {
panic!("The result of get_addr() should be an IPv6 address");
}
}
#[tokio::test]
async fn test_local_ipv4_is_any() {
let addr = get_ip(IpType::Ipv4, IpScope::Local, None).await;
assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
assert!(
addr.unwrap().is_ipv4(),
"The result of get_addr() should be an IPv4 address"
);
}
#[tokio::test]
async fn test_local_ipv6_is_any() {
if !has_any_ipv6_address(None, true) {
return;
}
let addr = get_ip(IpType::Ipv6, IpScope::Local, None).await;
assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
assert!(
addr.unwrap().is_ipv6(),
"The result of get_addr() should be an IPv6 address"
);
}
}