Skip to content

Commit

Permalink
avoid redundant annoucement or query packets (#135)
Browse files Browse the repository at this point in the history
cargo test seems flaky locally when there are many (e.g. 10) IPv6 interfaces on the same subnet. Sometimes IPv4 addr is not resolved in time, or the IPv4 packet seems to be lost.

One reason this could happen is that currently we always send out query packets and announcement packets on every address if they are all on the same subnet. This can cause a small "packet storm" if there are many addrs on the same subnet, especially link-local addrs.

In reality, we only need to send these packets once for one subnet, except the retransmissions per the RFC. This diff is to avoid such redundant packets on the same subnet.
  • Loading branch information
keepsimple1 authored Oct 14, 2023
1 parent fef5409 commit 545631a
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 4 deletions.
17 changes: 16 additions & 1 deletion src/service_daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::{
TYPE_TXT,
},
error::{Error, Result},
service_info::{split_sub_domain, ServiceInfo},
service_info::{ifaddr_netmask, split_sub_domain, ServiceInfo},
Receiver,
};
use flume::{bounded, Sender, TrySendError};
Expand Down Expand Up @@ -735,6 +735,7 @@ impl Zeroconf {
let my_ifaddrs = my_ip_interfaces();

// Create a socket for every IP addr.
// Note: it is possible that `my_ifaddrs` contains duplicated IP addrs.
let mut intf_socks = HashMap::new();
for intf in my_ifaddrs {
let sock = match new_socket_bind(&intf) {
Expand Down Expand Up @@ -934,8 +935,15 @@ impl Zeroconf {
/// Returns the list of interface IPs that sent out the annoucement.
fn send_unsolicited_response(&self, info: &ServiceInfo) -> Vec<IpAddr> {
let mut outgoing_addrs = Vec::new();
let mut netmask_set: HashSet<u128> = HashSet::new();

for (_, intf_sock) in self.intf_socks.iter() {
let netmask = ifaddr_netmask(&intf_sock.intf.addr);
if netmask_set.contains(&netmask) {
continue; // no need to send again in the same subnet.
}
if self.broadcast_service_on_intf(info, intf_sock) {
netmask_set.insert(netmask);
outgoing_addrs.push(intf_sock.intf.ip());
}
}
Expand Down Expand Up @@ -1103,7 +1111,14 @@ impl Zeroconf {
debug!("Sending multicast query for {}", name);
let mut out = DnsOutgoing::new(FLAGS_QR_QUERY);
out.add_question(name, qtype);

let mut netmask_set: HashSet<u128> = HashSet::new();
for (_, intf_sock) in self.intf_socks.iter() {
let netmask = ifaddr_netmask(&intf_sock.intf.addr);
if netmask_set.contains(&netmask) {
continue; // no need to send query the same subnet again.
}
netmask_set.insert(netmask);
broadcast_dns_on_intf(&out, intf_sock);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/service_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,14 @@ pub(crate) fn valid_ip_on_intf(addr: &IpAddr, intf: &Interface) -> bool {
}
}

/// Returns the netmask part of `addr` as `u128` for IPv4 and IPv6 address.
pub(crate) fn ifaddr_netmask(addr: &IfAddr) -> u128 {
match addr {
IfAddr::V4(addrv4) => u32::from(addrv4.netmask) as u128,
IfAddr::V6(addrv6) => u128::from(addrv6.netmask),
}
}

#[cfg(test)]
mod tests {
use super::{decode_txt, encode_txt, u8_slice_to_hex, ServiceInfo, TxtProperty};
Expand Down
8 changes: 5 additions & 3 deletions tests/mdns_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn integration_success() {
let ifaddrs_set: HashSet<_> = my_ip_interfaces().iter().map(|intf| intf.ip()).collect();
let my_ifaddrs: Vec<_> = ifaddrs_set.into_iter().collect();
let my_addrs_count = my_ifaddrs.len();
println!("My IP addr(s): {:?}", &my_ifaddrs);
println!("My IP {} addr(s): {:?}", my_ifaddrs.len(), &my_ifaddrs);

let host_name = "my_host.";
let port = 5200;
Expand Down Expand Up @@ -68,10 +68,12 @@ fn integration_success() {
println!("Found a new service: {}", &fullname);
}
ServiceEvent::ServiceResolved(info) => {
let addrs = info.get_addresses();
println!(
"Resolved a new service: {} addr(s): {:?}",
"Resolved a new service: {} with {} addr(s): {:?}",
info.get_fullname(),
info.get_addresses()
addrs.len(),
addrs
);
if info.get_fullname().contains(&instance_name) {
let mut num = resolve_count_clone.lock().unwrap();
Expand Down

0 comments on commit 545631a

Please sign in to comment.