Skip to content

Commit

Permalink
Merge pull request #58 from eladyn/duplicate_joins_ipv4
Browse files Browse the repository at this point in the history
avoid joining multiple times on the same interface for ipv4
  • Loading branch information
willstott101 authored Aug 24, 2024
2 parents faa90de + b483fe3 commit 712ec0b
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 45 deletions.
6 changes: 0 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ thiserror = "1.0"
tokio = { version = "1.0", features = ["sync", "net", "rt"] }
socket2 = { version = "0.5", features = ["all"] }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["netioapi"] }

[target.'cfg(not(windows))'.dependencies]
nix = { version = "0.28", features = ["net"] }

[dev-dependencies]
env_logger = { version = "0.10.2", default-features = false, features = [
"color",
Expand Down
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 712ec0b

Please sign in to comment.