Skip to content

Commit

Permalink
Add interface to get local IPv6 address (#105)
Browse files Browse the repository at this point in the history
Similar to local_ip() add local_ipv6() to get the local IPv6 address.
  • Loading branch information
cgzones authored Apr 30, 2023
1 parent 8be57e8 commit 788c02e
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 30 deletions.
11 changes: 8 additions & 3 deletions examples/show_ip_and_ifs.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
72 changes: 71 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -160,6 +160,76 @@ pub fn local_ip() -> Result<IpAddr, Error> {
}
}

/// 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<IpAddr, Error> {
#[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",
Expand Down
117 changes: 91 additions & 26 deletions src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<IpAddr, Error> {
local_ip_impl(Inet)
}

/// Retrieves the local IPv6 address for this system
pub fn local_ipv6() -> Result<IpAddr, Error> {
local_ip_impl(Inet6)
}

fn local_ip_impl(family: RtAddrFamily) -> Result<IpAddr, Error> {
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,
Expand All @@ -59,9 +83,14 @@ pub fn local_ip() -> Result<IpAddr, Error> {
.map_err(|err| Error::StrategyError(err.to_string()))?;

for response in netlink_socket.iter(false) {
let header: Nlmsghdr<Rtm, Rtmsg> = response.map_err(|_| {
Error::StrategyError(String::from(
"An error occurred retrieving Netlink's socket response",
let header: Nlmsghdr<Rtm, Rtmsg> = 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}",
))
})?;

Expand All @@ -85,22 +114,40 @@ pub fn local_ip() -> Result<IpAddr, Error> {
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::<u32>().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::<u32>().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::<u128>().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,
Expand Down Expand Up @@ -147,16 +194,34 @@ pub fn local_ip() -> Result<IpAddr, Error> {
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::<u32>().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::<u32>().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::<u128>().map_err(|_| {
Error::StrategyError(String::from(
"An error occurred retrieving Netlink's route payload attribute",
))
})?,
));
return Ok(IpAddr::V6(addr));
}
}
}
}
Expand Down

0 comments on commit 788c02e

Please sign in to comment.