From a8864420d74842a9ff66398045bdc7204edd6fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Tue, 28 Feb 2023 18:10:26 +0100 Subject: [PATCH] Add interface to get local IPv6 address Similar to local_ip() add local_ipv6() to get the local IPv6 address. --- examples/show_ip_and_ifs.rs | 11 +++- src/lib.rs | 72 +++++++++++++++++++++- src/linux.rs | 117 ++++++++++++++++++++++++++++-------- 3 files changed, 170 insertions(+), 30 deletions(-) diff --git a/examples/show_ip_and_ifs.rs b/examples/show_ip_and_ifs.rs index 260f54f..33ee3ec 100644 --- a/examples/show_ip_and_ifs.rs +++ b/examples/show_ip_and_ifs.rs @@ -1,9 +1,14 @@ -use local_ip_address::{list_afinet_netifas, local_ip}; +use local_ip_address::{list_afinet_netifas, local_ip, local_ipv6}; fn main() { match local_ip() { - Ok(ip) => println!("Local IP: {}", ip), - Err(err) => println!("Failed to get local IP: {}", err), + Ok(ip) => println!("Local IPv4: {}", ip), + Err(err) => println!("Failed to get local IPv4: {}", err), + }; + + match local_ipv6() { + Ok(ip) => println!("Local IPv6: {}", ip), + Err(err) => println!("Failed to get local IPv6: {}", err), }; match list_afinet_netifas() { diff --git a/src/lib.rs b/src/lib.rs index 5a78c2e..c62973d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ pub mod windows; #[cfg(target_family = "windows")] pub use crate::windows::*; -/// Retrieves the local ip address of the machine in the local network from +/// Retrieves the local IPv4 address of the machine in the local network from /// the `AF_INET` family. /// /// A different approach is taken based on the operative system. @@ -160,6 +160,76 @@ pub fn local_ip() -> Result { } } +/// Retrieves the local IPv6 address of the machine in the local network from +/// the `AF_INET6` family. +/// +/// A different approach is taken based on the operative system. +/// +/// For linux based systems the Netlink socket communication is used to +/// retrieve the local network interface. +/// +/// For BSD-based systems the `getifaddrs` approach is taken using `libc` +/// +/// For Windows systems Win32's IP Helper is used to gather the Local IP +/// address +pub fn local_ipv6() -> Result { + #[cfg(target_os = "linux")] + { + crate::linux::local_ipv6() + } + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "android", + ))] + { + let ifas = crate::unix::list_afinet_netifas_info()?; + + ifas.into_iter() + .find_map(|ifa| { + if !ifa.is_loopback && ifa.addr.is_ipv6() { + Some(ifa.addr) + } else { + None + } + }) + .ok_or(Error::LocalIpAddressNotFound) + } + + #[cfg(target_os = "windows")] + { + use windows_sys::Win32::Networking::WinSock::AF_INET6; + + let ip_addresses = crate::windows::list_local_ip_addresses(AF_INET6)?; + + ip_addresses + .into_iter() + .find(|ip_address| matches!(ip_address, IpAddr::V6(_))) + .ok_or(Error::LocalIpAddressNotFound) + } + + // A catch-all case to error if not implemented for OS + #[cfg(not(any( + target_os = "linux", + target_os = "windows", + target_os = "macos", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "android", + )))] + { + Err(Error::PlatformNotSupported( + std::env::consts::OS.to_string(), + )) + } +} + // A catch-all function to error if not implemented for OS #[cfg(not(any( target_os = "linux", diff --git a/src/linux.rs b/src/linux.rs index a07ca1c..1dadc26 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -14,6 +14,7 @@ use neli::rtnl::{Ifaddrmsg, Ifinfomsg, Rtattr, Rtmsg}; use neli::socket::NlSocketHandle; use neli::types::RtBuffer; use neli::consts::rtnl::RtAddrFamily::{Inet, Inet6}; +use neli::err::NlError::Nlmsgerr; use crate::Error; @@ -22,19 +23,42 @@ const RTM_FLAGS_LOOKUP: &[RtmF] = &[RtmF::LookupTable]; #[cfg(not(target_env = "gnu"))] const RTM_FLAGS_LOOKUP: &[RtmF] = &[]; -/// Retrieves the local IP address for this system +/// Retrieves the local IPv4 address for this system pub fn local_ip() -> Result { + local_ip_impl(Inet) +} + +/// Retrieves the local IPv6 address for this system +pub fn local_ipv6() -> Result { + local_ip_impl(Inet6) +} + +fn local_ip_impl(family: RtAddrFamily) -> Result { let mut netlink_socket = NlSocketHandle::connect(NlFamily::Route, None, &[]) .map_err(|err| Error::StrategyError(err.to_string()))?; - let dstip = Ipv4Addr::new(192, 0, 2, 0); // reserved external IP - let raw_dstip = u32::from(dstip).to_be(); - let route_attr = Rtattr::new(None, Rta::Dst, raw_dstip) - .map_err(|err| Error::StrategyError(err.to_string()))?; + let route_attr = match family { + Inet => { + let dstip = Ipv4Addr::new(192, 0, 2, 0); // reserved external IP + let raw_dstip = u32::from(dstip).to_be(); + Rtattr::new(None, Rta::Dst, raw_dstip) + } + Inet6 => { + let dstip = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0); // reserved external IP + let raw_dstip = u128::from(dstip).to_be(); + Rtattr::new(None, Rta::Dst, raw_dstip) + } + _ => Err(Error::StrategyError(format!( + "Invalid address family given: {:#?}", + family + )))?, + }; + + let route_attr = route_attr.map_err(|err| Error::StrategyError(err.to_string()))?; let mut route_payload = RtBuffer::new(); route_payload.push(route_attr); let ifroutemsg = Rtmsg { - rtm_family: RtAddrFamily::Inet, + rtm_family: family, rtm_dst_len: 0, rtm_src_len: 0, rtm_tos: 0, @@ -59,9 +83,14 @@ pub fn local_ip() -> Result { .map_err(|err| Error::StrategyError(err.to_string()))?; for response in netlink_socket.iter(false) { - let header: Nlmsghdr = response.map_err(|_| { - Error::StrategyError(String::from( - "An error occurred retrieving Netlink's socket response", + let header: Nlmsghdr = response.map_err(|err| { + if let Nlmsgerr(ref err) = err { + if err.error == -libc::ENETUNREACH { + return Error::LocalIpAddressNotFound; + } + } + Error::StrategyError(format!( + "An error occurred retrieving Netlink's socket response: {err}", )) })?; @@ -85,22 +114,40 @@ pub fn local_ip() -> Result { continue; } + if p.rtm_family != family { + Err(Error::StrategyError(format!( + "Invalid address family in Netlink payload: {:?}", + p.rtm_family + )))? + } + for rtattr in p.rtattrs.iter() { if rtattr.rta_type == Rta::Prefsrc { - let addr = Ipv4Addr::from(u32::from_be(rtattr.get_payload_as::().map_err( - |_| { - Error::StrategyError(String::from( - "An error occurred retrieving Netlink's route payload attribute", - )) - }, - )?)); - return Ok(IpAddr::V4(addr)); + if p.rtm_family == Inet { + let addr = Ipv4Addr::from(u32::from_be( + rtattr.get_payload_as::().map_err(|_| { + Error::StrategyError(String::from( + "An error occurred retrieving Netlink's route payload attribute", + )) + })?, + )); + return Ok(IpAddr::V4(addr)); + } else { + let addr = Ipv6Addr::from(u128::from_be( + rtattr.get_payload_as::().map_err(|_| { + Error::StrategyError(String::from( + "An error occurred retrieving Netlink's route payload attribute", + )) + })?, + )); + return Ok(IpAddr::V6(addr)); + } } } } let ifaddrmsg = Ifaddrmsg { - ifa_family: RtAddrFamily::Inet, + ifa_family: family, ifa_prefixlen: 0, ifa_flags: IfaFFlags::empty(), ifa_scope: 0, @@ -147,16 +194,34 @@ pub fn local_ip() -> Result { continue; } + if p.ifa_family != family { + Err(Error::StrategyError(format!( + "Invalid family in Netlink payload: {:?}", + p.ifa_family + )))? + } + for rtattr in p.rtattrs.iter() { if rtattr.rta_type == Ifa::Local { - let addr = Ipv4Addr::from(u32::from_be(rtattr.get_payload_as::().map_err( - |_| { - Error::StrategyError(String::from( - "An error occurred retrieving Netlink's route payload attribute", - )) - }, - )?)); - return Ok(IpAddr::V4(addr)); + if p.ifa_family == Inet { + let addr = Ipv4Addr::from(u32::from_be( + rtattr.get_payload_as::().map_err(|_| { + Error::StrategyError(String::from( + "An error occurred retrieving Netlink's route payload attribute", + )) + })?, + )); + return Ok(IpAddr::V4(addr)); + } else { + let addr = Ipv6Addr::from(u128::from_be( + rtattr.get_payload_as::().map_err(|_| { + Error::StrategyError(String::from( + "An error occurred retrieving Netlink's route payload attribute", + )) + })?, + )); + return Ok(IpAddr::V6(addr)); + } } } }