blob: fd482f1dc3fccd813848d2f6b63ee5bdbcb5151d [file] [log] [blame]
// 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());
}
}