| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| use std::net::IpAddr; |
| use std::net::Ipv6Addr; |
| |
| use libc; |
| |
| pub trait IsLinkLocal { |
| fn is_ll(&self) -> bool; |
| } |
| |
| impl IsLinkLocal for Ipv6Addr { |
| /// Returns true if this is a link local address: |
| /// https://tools.ietf.org/html/rfc4291 |
| fn is_ll(&self) -> bool { |
| self.segments()[0] == 0xfe80 |
| && self.segments()[1] == 0x0000 |
| && self.segments()[2] == 0x0000 |
| && self.segments()[3] == 0x0000 |
| } |
| } |
| |
| pub trait IsMcast { |
| /// Determines if some kind of flags or some interface is multicast. |
| fn is_mcast(&self) -> bool; |
| } |
| |
| impl IsMcast for i32 { |
| /// Tests if the ifa_flags portion of an ifaddrs struct is deemed usable |
| /// for multicast: |
| /// -- The interface is up. |
| /// -- Mcast bit is set. |
| /// -- The interface is not a loopback. |
| fn is_mcast(&self) -> bool { |
| self & libc::IFF_UP != 0 |
| && self & libc::IFF_LOOPBACK == 0 |
| && self & libc::IFF_MULTICAST != 0 |
| } |
| } |
| |
| /// An Mcast interface is: |
| /// -- Not a loopback |
| /// -- Up (as opposed to down). |
| /// -- Has mcast enabled |
| #[derive(Debug, Hash, Clone, Eq, PartialEq)] |
| pub struct McastInterface { |
| pub name: String, |
| pub id: u32, |
| pub addrs: Vec<IpAddr>, |
| } |
| |
| // TODO(fxb/44855): This needs to be e2e tested. |
| #[cfg(target_os = "linux")] |
| pub mod linux { |
| use super::*; |
| use std::collections::HashMap; |
| use std::io; |
| use std::mem; |
| |
| pub fn sockaddr_to_ip(sockaddr: *const libc::sockaddr) -> Option<IpAddr> { |
| if sockaddr.is_null() { |
| return None; |
| } |
| let family = i32::from(unsafe { *sockaddr }.sa_family); |
| match family { |
| libc::AF_INET => { |
| let sockaddr = &unsafe { *(sockaddr as *const libc::sockaddr_in) }; |
| let addr_raw = sockaddr.sin_addr.s_addr; |
| // `to_be()` should be a no-op here, but just in case. |
| Some(IpAddr::V4(addr_raw.to_be().into())) |
| } |
| libc::AF_INET6 => { |
| let sockaddr = &unsafe { *(sockaddr as *const libc::sockaddr_in6) }; |
| let ip: Ipv6Addr = sockaddr.sin6_addr.s6_addr.into(); |
| // Only link-local is supported for the kind of mcast we're going |
| // to be doing. Globally routable addresses appear to panic when |
| // calling bind() in UdpBuilder. |
| if ip.is_ll() { |
| Some(IpAddr::V6(ip)) |
| } else { |
| None |
| } |
| } |
| _ => None, |
| } |
| } |
| |
| pub unsafe fn get_mcast_interfaces() -> io::Result<Vec<McastInterface>> { |
| let mut res = HashMap::<u32, McastInterface>::new(); |
| let mut ifaddrs = mem::MaybeUninit::uninit().as_mut_ptr(); |
| if -1 == libc::getifaddrs(&mut ifaddrs) { |
| return Err(io::Error::last_os_error()); |
| } |
| |
| let mut ifaddrs_iter = ifaddrs; |
| loop { |
| if ifaddrs_iter.is_null() { |
| break; |
| } |
| if let Some(ip) = sockaddr_to_ip((*ifaddrs_iter).ifa_addr) { |
| let name = std::ffi::CStr::from_ptr((*ifaddrs_iter).ifa_name as *const _) |
| .to_string_lossy() |
| .into_owned(); |
| let id = libc::if_nametoindex((*ifaddrs_iter).ifa_name as *const _); |
| let flags = (*ifaddrs_iter).ifa_flags as i32; |
| |
| if flags.is_mcast() { |
| match res.get_mut(&id) { |
| Some(iface) => iface.addrs.push(ip), |
| None => { |
| res.insert(id, McastInterface { name, id, addrs: vec![ip] }); |
| } |
| } |
| } |
| } |
| ifaddrs_iter = (*ifaddrs_iter).ifa_next; |
| } |
| libc::freeifaddrs(ifaddrs); |
| Ok(res.iter().map(|(_, v)| v.clone()).collect()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_is_link_local() { |
| let is_ll = Ipv6Addr::new(0xfe80, 0, 0, 0, 1, 6, 7, 8); |
| let is_not_ll = Ipv6Addr::new(0xfe81, 2, 3, 4, 5, 6, 7, 8); |
| assert!(is_ll.is_ll()); |
| assert!(!is_not_ll.is_ll()); |
| } |
| |
| #[test] |
| fn test_mcast_flags() { |
| // Expected interface. |
| assert!((libc::IFF_UP | libc::IFF_MULTICAST).is_mcast()); |
| // Loopback. |
| assert!(!(libc::IFF_UP | libc::IFF_MULTICAST | libc::IFF_LOOPBACK).is_mcast()); |
| // Down interface. |
| assert!(!(libc::IFF_MULTICAST).is_mcast()); |
| } |
| } |