Skip to content

Commit

Permalink
avoid joining multiple times on the same interface for ipv4
Browse files Browse the repository at this point in the history
The mechanism for IPv6 is extended to avoid multiple
joins on the same IPv4 interface which leads to Address already in use.
  • Loading branch information
eladyn committed Aug 19, 2024
1 parent faa90de commit f38e8b1
Showing 1 changed file with 18 additions and 39 deletions.
57 changes: 18 additions & 39 deletions src/address_family.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
use super::MDNS_PORT;
use if_addrs::get_if_addrs;
use if_addrs::{get_if_addrs, Interface};
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
use std::collections::HashSet;
use std::io;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};

#[cfg(not(windows))]
use nix::net::if_::if_nametoindex;

#[cfg(windows)]
use win::if_nametoindex;

pub enum Inet {}

pub enum Inet6 {}
Expand Down Expand Up @@ -52,12 +47,12 @@ impl AddressFamily for Inet {
const DOMAIN: Domain = Domain::IPV4;

fn join_multicast(socket: &Socket, multiaddr: &Self::Addr) -> io::Result<()> {
let addresses = get_address_list()?;
if addresses.is_empty() {
let ifaces = get_iface_list()?;
if ifaces.is_empty() {
socket.join_multicast_v4(multiaddr, &Ipv4Addr::UNSPECIFIED)
} else {
for (_, address) in addresses {
if let IpAddr::V4(ip) = address {
for iface in ifaces {
if let IpAddr::V4(ip) = iface.ip() {
socket.join_multicast_v4(multiaddr, &ip)?;
}
}
Expand All @@ -75,44 +70,28 @@ impl AddressFamily for Inet6 {
const DOMAIN: Domain = Domain::IPV6;

fn join_multicast(socket: &Socket, multiaddr: &Self::Addr) -> io::Result<()> {
let addresses = get_address_list()?;
if addresses.is_empty() {
let ifaces = get_iface_list()?;
if ifaces.is_empty() {
socket.join_multicast_v6(multiaddr, 0)
} else {
// We join multicast by interface, but each interface can have more than one ipv6 address.
// So we have to check we're not registering more than once, as the resulting error is then
// fatal to ipv6 listening.
// TODO: Make each interface resilient to failures on another.
let mut registered = Vec::new();
for (iface_name, address) in addresses {
if let IpAddr::V6(_) = address {
let ipv6_index = if_nametoindex(iface_name.as_str()).unwrap_or(0);
if ipv6_index != 0 && !registered.contains(&ipv6_index) {
socket.join_multicast_v6(multiaddr, ipv6_index)?;
registered.push(ipv6_index);
}
for iface in ifaces {
if let (IpAddr::V6(_), Some(ipv6_index)) = (iface.ip(), iface.index) {
socket.join_multicast_v6(multiaddr, ipv6_index)?;
}
}
Ok(())
}
}
}

fn get_address_list() -> io::Result<Vec<(String, IpAddr)>> {
fn get_iface_list() -> io::Result<Vec<Interface>> {
// There may be multiple ip addresses on a single interface and we join multicast by interface.
// Joining multicast on the same interface multiple times returns an error
// so we filter duplicate interfaces.
let mut collected_interfaces = HashSet::new();
Ok(get_if_addrs()?
.iter()
.filter(|iface| !iface.is_loopback())
.map(|iface| (iface.name.clone(), iface.ip()))
.into_iter()
.filter(|iface| !iface.is_loopback() && collected_interfaces.insert(iface.name.clone()))
.collect())
}

#[cfg(windows)]
mod win {
use std::ffi::{CString, NulError};
use winapi::shared::netioapi;

pub fn if_nametoindex(ifname: &str) -> Result<u32, NulError> {
let c_str = CString::new(ifname)?;
Ok(unsafe { netioapi::if_nametoindex(c_str.as_ptr()) })
}
}

0 comments on commit f38e8b1

Please sign in to comment.