diff --git a/examples/show_ip_and_ifs.rs b/examples/show_ip_and_ifs.rs index 33ee3ec..ea290e5 100644 --- a/examples/show_ip_and_ifs.rs +++ b/examples/show_ip_and_ifs.rs @@ -1,4 +1,9 @@ -use local_ip_address::{list_afinet_netifas, local_ip, local_ipv6}; +use local_ip_address::{ + list_afinet_netifas, + local_ip, + local_ipv6, + local_broadcast_ip, +}; fn main() { match local_ip() { @@ -11,6 +16,11 @@ fn main() { Err(err) => println!("Failed to get local IPv6: {}", err), }; + match local_broadcast_ip() { + Ok(ip) => println!("Local broadcast IPv4: {}", ip), + Err(err) => println!("Failed to get local broadcast IPv4: {}", err), + }; + match list_afinet_netifas() { Ok(netifs) => { println!("Got {} interfaces", netifs.len()); @@ -21,3 +31,4 @@ fn main() { Err(err) => println!("Failed to get list of network interfaces: {}", err), }; } + diff --git a/src/linux.rs b/src/linux.rs index fe99c96..2fea526 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -34,6 +34,244 @@ pub fn local_ipv6() -> Result { local_ip_impl(Inet6) } +/// Retrieves the local broadcast IPv4 address for this system +pub fn local_broadcast_ip() -> Result { + local_broadcast_impl(Inet) +} + +fn local_broadcast_impl(family: RtAddrFamily) -> Result { + let mut netlink_socket = NlSocketHandle::connect(NlFamily::Route, None, &[]) + .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: family, + rtm_dst_len: 0, + rtm_src_len: 0, + rtm_tos: 0, + rtm_table: RtTable::Unspec, + rtm_protocol: Rtprot::Unspec, + rtm_scope: RtScope::Universe, + rtm_type: Rtn::Unspec, + rtm_flags: RtmFFlags::new(RTM_FLAGS_LOOKUP), + rtattrs: route_payload, + }; + let netlink_message = Nlmsghdr::new( + None, + Rtm::Getroute, + NlmFFlags::new(&[NlmF::Request]), + None, + None, + NlPayload::Payload(ifroutemsg), + ); + + netlink_socket + .send(netlink_message) + .map_err(|err| Error::StrategyError(err.to_string()))?; + + let mut pref_ip = None; + for response in netlink_socket.iter(false) { + if pref_ip.is_some() { + break; + } + 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}", + )) + })?; + + if let NlPayload::Empty = header.nl_payload { + continue; + } + + if header.nl_type != Rtm::Newroute { + return Err(Error::StrategyError(String::from( + "The Netlink header type is not the expected", + ))); + } + + let p = header.get_payload().map_err(|_| { + Error::StrategyError(String::from( + "An error occurred getting Netlink's header payload", + )) + })?; + + if p.rtm_scope != RtScope::Universe { + 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 { + 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", + )) + })?, + )); + pref_ip = Some(IpAddr::V4(addr)); + break; + } + 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", + )) + })?, + )); + pref_ip = Some(IpAddr::V6(addr)); + break; + } + } + } + + let ifaddrmsg = Ifaddrmsg { + ifa_family: family, + ifa_prefixlen: 0, + ifa_flags: IfaFFlags::empty(), + ifa_scope: 0, + ifa_index: 0, + rtattrs: RtBuffer::new(), + }; + let netlink_message = Nlmsghdr::new( + None, + Rtm::Getaddr, + NlmFFlags::new(&[NlmF::Request, NlmF::Root]), + None, + None, + NlPayload::Payload(ifaddrmsg), + ); + + netlink_socket + .send(netlink_message) + .map_err(|err| Error::StrategyError(err.to_string()))?; + + let mut broadcast_ip = None; + 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", + )) + })?; + + if let NlPayload::Empty = header.nl_payload { + continue; + } + + if header.nl_type != Rtm::Newaddr { + return Err(Error::StrategyError(String::from( + "The Netlink header type is not the expected", + ))); + } + + let p = header.get_payload().map_err(|_| { + Error::StrategyError(String::from( + "An error occurred getting Netlink's header payload", + )) + })?; + + if RtScope::from(p.ifa_scope) != RtScope::Universe { + continue; + } + + if p.ifa_family != family { + Err(Error::StrategyError(format!( + "Invalid family in Netlink payload: {:?}", + p.ifa_family + )))? + } + + let mut is_match = false; + for rtattr in p.rtattrs.iter() { + if rtattr.rta_type == Ifa::Local { + 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", + )) + })?, + )); + is_match = pref_ip == Some(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", + )) + })?, + )); + is_match = pref_ip == Some(IpAddr::V6(addr)); + } + } + if is_match { + if rtattr.rta_type == Ifa::Broadcast { + 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 broadcast attribute", + )) + })?, + )); + return Ok(IpAddr::V4(addr)); + } + } + } + if rtattr.rta_type == Ifa::Broadcast { + 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", + )) + })?, + )); + broadcast_ip = Some(IpAddr::V4(addr)); + } + } + } + } + + if let Some(broadcast_ip) = broadcast_ip { + return Ok(broadcast_ip); + } + + Err(Error::LocalIpAddressNotFound) +} + fn local_ip_impl(family: RtAddrFamily) -> Result { let mut netlink_socket = NlSocketHandle::connect(NlFamily::Route, None, &[]) .map_err(|err| Error::StrategyError(err.to_string()))?; @@ -482,3 +720,4 @@ mod tests { assert_eq!(res.unwrap(), expected); } } +