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)]
57pub enum IpScope {
58 Global,
62 Local,
66}
67
68#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
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] crate::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, libc_getips, IpScope, IpType};
179 use std::net::IpAddr;
180
181 fn has_any_ipv6_address(iface_name: Option<&str>, global: bool) -> bool {
184 if let Ok(addresses) = libc_getips::get_iface_addrs(Some(IpType::Ipv6), iface_name) {
185 addresses
186 .iter()
187 .filter(|ip| {
188 !ip.is_loopback()
189 && !ip.is_unspecified()
190 && if global {
191 if let IpAddr::V6(dcasted) = ip {
192 (dcasted.segments()[0] & 0xffc0) != 0xfe80
194 && (dcasted.segments()[0] & 0xfe00) != 0xfc00
196 } else {
197 unreachable!()
198 }
199 } else {
200 true
201 }
202 })
203 .next()
204 .is_some()
205 } else {
206 false
207 }
208 }
209
210 #[tokio::test]
211 async fn test_global_ipv4_is_any() {
212 let addr = get_ip(IpType::Ipv4, IpScope::Global, None).await;
213 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
214 if let IpAddr::V4(addr) = addr.unwrap() {
215 assert!(
216 !addr.is_private(),
217 "The result of get_addr() should not be private"
218 );
219 assert!(
220 !addr.is_loopback(),
221 "The result of get_addr() should not be loopback"
222 );
223 } else {
224 assert!(false, "The result of get_addr() should be an IPv4 address");
225 }
226 }
227
228 #[tokio::test]
229 async fn test_global_ipv6_is_any() {
230 if !has_any_ipv6_address(None, true) {
231 return;
232 }
233 let addr = get_ip(IpType::Ipv6, IpScope::Global, None).await;
234 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
235 if let IpAddr::V6(addr) = addr.unwrap() {
236 assert!(
237 !addr.is_loopback(),
238 "The result of get_addr() should not be loopback"
239 );
240 assert!(
241 !addr.is_unspecified(),
242 "The result of get_addr() should not be unspecified"
243 );
244 } else {
245 assert!(false, "The result of get_addr() should be an IPv6 address");
246 }
247 }
248
249 #[tokio::test]
250 async fn test_local_ipv4_is_any() {
251 let addr = get_ip(IpType::Ipv4, IpScope::Local, None).await;
252 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
253 if !addr.unwrap().is_ipv4() {
254 assert!(false, "The result of get_addr() should be an IPv4 address");
255 }
256 }
257
258 #[tokio::test]
259 async fn test_local_ipv6_is_any() {
260 if !has_any_ipv6_address(None, true) {
261 return;
262 }
263 let addr = get_ip(IpType::Ipv6, IpScope::Local, None).await;
264 assert!(addr.is_ok(), "The result of get_addr() should be Ok()");
265 if !addr.unwrap().is_ipv6() {
266 assert!(false, "The result of get_addr() should be an IPv6 address");
267 }
268 }
269}