1#![warn(
32 clippy::pedantic,
33 missing_docs,
34 missing_debug_implementations,
35 missing_copy_implementations,
36 trivial_casts,
37 trivial_numeric_casts,
38 unsafe_op_in_unsafe_fn,
39 unused_extern_crates,
40 unused_import_braces,
41 unused_qualifications,
42 variant_size_differences
43)]
44#![allow(clippy::no_effect_underscore_binding)]
45
46pub mod gip;
47pub mod hostip;
48pub mod libc_getips;
49
50use async_trait::async_trait;
51use serde::Deserialize;
52use std::net::IpAddr;
53use thiserror::Error;
54
55#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
57pub enum IpScope {
58 Global,
62 Local,
66}
67
68#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
70pub enum IpType {
71 #[serde(rename = "IPv4")]
73 Ipv4,
74 #[serde(rename = "IPv6")]
76 Ipv6,
77}
78
79#[derive(Debug, Error)]
81pub enum Error {
82 #[error(transparent)]
84 GlobalIpError(#[from] gip::GlobalIpError),
85
86 #[error(transparent)]
88 AddrParseError(#[from] std::net::AddrParseError),
89
90 #[error(transparent)]
92 UnicodeParseError(#[from] std::string::FromUtf8Error),
93
94 #[error("Command exited with status {0}")]
96 NonZeroExit(std::process::ExitStatus),
97
98 #[error(transparent)]
100 IoError(#[from] std::io::Error),
101
102 #[error("no address returned")]
104 NoAddress,
105}
106
107pub type Result<R> = std::result::Result<R, Error>;
109
110#[async_trait]
112pub trait Provider: Sized {
113 async fn get_addr(&self) -> Result<IpAddr>;
115 fn get_type(&self) -> IpType;
117}
118
119pub async fn get_ip(ip_type: IpType, ip_scope: IpScope, nic: Option<&str>) -> Result<IpAddr> {
145 match (ip_type, ip_scope) {
146 (IpType::Ipv4, IpScope::Global) => {
147 let p = gip::ProviderMultiple::default();
149 p.get_addr().await
150 }
151 (IpType::Ipv6, IpScope::Global) => {
152 let p = gip::ProviderMultiple::default_v6();
154 p.get_addr().await
155 }
156 (IpType::Ipv4, IpScope::Local) => {
157 let p = hostip::LocalLibcProvider::new(nic, IpType::Ipv4);
159 p.get_addr().await
160 }
161 (IpType::Ipv6, IpScope::Local) => {
162 if let Some(nic) = nic {
164 let command_provider = hostip::LocalIpv6CommandProvider::new(nic, true);
165 let command_result = command_provider.get_addr().await;
166 if command_result.is_ok() || matches!(command_result, Err(Error::NoAddress)) {
167 return command_result;
168 }
169 }
170 let p = hostip::LocalLibcProvider::new(nic, IpType::Ipv6);
171 p.get_addr().await
172 }
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use crate::{get_ip, gip::ProviderMultiple, libc_getips, IpScope, IpType, Provider};
179 use std::net::IpAddr;
180
181 fn has_any_ipv6_address(iface_name: Option<&str>, global: bool) -> bool {
184 libc_getips::get_iface_addrs(Some(IpType::Ipv6), iface_name).is_ok_and(|addresses| {
185 addresses.iter().any(|ip| {
186 !ip.is_loopback()
187 && !ip.is_unspecified()
188 && if global {
189 if let IpAddr::V6(dcasted) = ip {
190 (dcasted.segments()[0] & 0xffc0) != 0xfe80
192 && (dcasted.segments()[0] & 0xfe00) != 0xfc00
194 } else {
195 unreachable!()
196 }
197 } else {
198 true
199 }
200 })
201 })
202 }
203
204 #[tokio::test]
205 async fn test_global_ipv4_is_any() {
206 let addr = get_ip(IpType::Ipv4, IpScope::Global, None).await;
207 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
208 if let IpAddr::V4(addr) = addr.unwrap() {
209 assert!(
210 !addr.is_private(),
211 "The result of get_addr() should not be private"
212 );
213 assert!(
214 !addr.is_loopback(),
215 "The result of get_addr() should not be loopback"
216 );
217 } else {
218 panic!("The result of get_addr() should be an IPv4 address");
219 }
220 }
221
222 #[tokio::test]
223 async fn test_global_ipv4_just_dns_is_any() {
224 const DNS_PROVIDERS: &str = r#"[
225 {
226 "method": "dns",
227 "name": "opendns.com",
228 "type": "IPv4",
229 "url": "myip.opendns.com@resolver1.opendns.com"
230 },
231 {
232 "method": "dns",
233 "name": "opendns.com",
234 "type": "IPv6",
235 "url": "myip.opendns.com@resolver1.opendns.com"
236 },
237 {
238 "method": "dns",
239 "name": "akamai.com",
240 "type": "IPv4",
241 "url": "whoami.akamai.com@ns1-1.akamaitech.net"
242 }
243 ]"#;
244 let provider = ProviderMultiple::from_json(DNS_PROVIDERS).unwrap();
245 let addr = provider.get_addr().await;
246 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
247 }
248
249 #[tokio::test]
250 async fn test_global_ipv6_is_any() {
251 if !has_any_ipv6_address(None, true) {
252 return;
253 }
254 let addr = get_ip(IpType::Ipv6, IpScope::Global, None).await;
255 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
256 if let IpAddr::V6(addr) = addr.unwrap() {
257 assert!(
258 !addr.is_loopback(),
259 "The result of get_addr() should not be loopback"
260 );
261 assert!(
262 !addr.is_unspecified(),
263 "The result of get_addr() should not be unspecified"
264 );
265 } else {
266 panic!("The result of get_addr() should be an IPv6 address");
267 }
268 }
269
270 #[tokio::test]
271 async fn test_local_ipv4_is_any() {
272 let addr = get_ip(IpType::Ipv4, IpScope::Local, None).await;
273 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
274 assert!(
275 addr.unwrap().is_ipv4(),
276 "The result of get_addr() should be an IPv4 address"
277 );
278 }
279
280 #[tokio::test]
281 async fn test_local_ipv6_is_any() {
282 if !has_any_ipv6_address(None, true) {
283 return;
284 }
285 let addr = get_ip(IpType::Ipv6, IpScope::Local, None).await;
286 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
287 assert!(
288 addr.unwrap().is_ipv6(),
289 "The result of get_addr() should be an IPv6 address"
290 );
291 }
292}