diff --git a/Cargo.toml b/Cargo.toml index 28476214..1f00ec4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ default = ["reuseport", "async", "logging"] [dependencies] fastrand = "2.3" flume = { version = "0.11", default-features = false } # channel between threads -if-addrs = { version = "0.14", features = ["link-local"] } # get local IP addresses +if-addrs = { version = "0.15", features = ["link-local"] } # get local IP addresses log = { version = "0.4", optional = true } # logging mio = { version = "1.1", features = ["os-poll", "net"] } # select/poll sockets socket2 = { version = "0.6", features = ["all"] } # socket APIs diff --git a/examples/register.rs b/examples/register.rs index 30ac7711..612123e3 100644 --- a/examples/register.rs +++ b/examples/register.rs @@ -26,6 +26,7 @@ fn main() { let mut should_unreg = false; let mut disable_ipv6 = false; let mut use_logfile = false; + let mut include_apple_p2p = false; for arg in args.iter() { if arg.as_str() == "--unregister" { @@ -34,6 +35,8 @@ fn main() { disable_ipv6 = true; } else if arg.as_str() == "--logfile" { use_logfile = true; + } else if arg.as_str() == "--include-apple-p2p" { + include_apple_p2p = true; } } @@ -60,6 +63,10 @@ fn main() { mdns.disable_interface(IfKind::IPv6).unwrap(); } + if include_apple_p2p { + mdns.include_apple_p2p(true).unwrap(); + } + let service_type = match args.get(1) { Some(arg) => format!("{}.local.", arg), None => { @@ -140,6 +147,7 @@ fn print_usage() { println!("--unregister: automatically unregister after 2 seconds"); println!("--disable-ipv6: not to use IPv6 interfaces."); println!("--logfile: write debug log to a file instead of stderr. The logfile is named 'mdns-register-.log'."); + println!("--include-apple-p2p: include Apple p2p interfaces (e.g., awdl, llw) for mDNS."); println!(); println!("For example:"); println!("cargo run --example register _my-hello._udp instance1 host1"); diff --git a/src/service_daemon.rs b/src/service_daemon.rs index 0e10c1a1..129cfbe0 100644 --- a/src/service_daemon.rs +++ b/src/service_daemon.rs @@ -570,6 +570,12 @@ impl ServiceDaemon { self.send_cmd(Command::SetOption(DaemonOption::AcceptUnsolicited(accept))) } + /// Include or exclude Apple P2P interfaces, e.g. "awdl0", "llw0". + /// By default, they are excluded. + pub fn include_apple_p2p(&self, include: bool) -> Result<()> { + self.send_cmd(Command::SetOption(DaemonOption::IncludeAppleP2P(include))) + } + #[cfg(test)] pub fn test_down_interface(&self, ifname: &str) -> Result<()> { self.send_cmd(Command::SetOption(DaemonOption::TestDownInterface( @@ -942,6 +948,8 @@ struct Zeroconf { accept_unsolicited: bool, + include_apple_p2p: bool, + cmd_sender: Sender, signal_addr: SocketAddr, @@ -1118,6 +1126,7 @@ impl Zeroconf { multicast_loop_v4: true, multicast_loop_v6: true, accept_unsolicited: false, + include_apple_p2p: false, cmd_sender, signal_addr, @@ -1404,6 +1413,7 @@ impl Zeroconf { DaemonOption::MulticastLoopV4(on) => self.set_multicast_loop_v4(on), DaemonOption::MulticastLoopV6(on) => self.set_multicast_loop_v6(on), DaemonOption::AcceptUnsolicited(accept) => self.set_accept_unsolicited(accept), + DaemonOption::IncludeAppleP2P(enable) => self.set_apple_p2p(enable), #[cfg(test)] DaemonOption::TestDownInterface(ifname) => { self.test_down_interfaces.insert(ifname); @@ -1424,7 +1434,7 @@ impl Zeroconf { }); } - self.apply_intf_selections(my_ip_interfaces(true)); + self.apply_intf_selections(my_ip_interfaces_inner(true, self.include_apple_p2p)); } fn disable_interface(&mut self, kinds: Vec) { @@ -1436,7 +1446,7 @@ impl Zeroconf { }); } - self.apply_intf_selections(my_ip_interfaces(true)); + self.apply_intf_selections(my_ip_interfaces_inner(true, self.include_apple_p2p)); } fn set_multicast_loop_v4(&mut self, on: bool) { @@ -1465,6 +1475,13 @@ impl Zeroconf { self.accept_unsolicited = accept; } + fn set_apple_p2p(&mut self, include: bool) { + if self.include_apple_p2p != include { + self.include_apple_p2p = include; + self.apply_intf_selections(my_ip_interfaces_inner(true, self.include_apple_p2p)); + } + } + fn notify_monitors(&mut self, event: DaemonEvent) { // Only retain the monitors that are still connected. self.monitors.retain(|sender| { @@ -1573,7 +1590,7 @@ impl Zeroconf { /// Check for IP changes and update [my_intfs] as needed. fn check_ip_changes(&mut self) { // Get the current interfaces. - let my_ifaddrs = my_ip_interfaces(true); + let my_ifaddrs = my_ip_interfaces_inner(true, self.include_apple_p2p); #[cfg(test)] let my_ifaddrs: Vec<_> = my_ifaddrs @@ -1869,7 +1886,8 @@ impl Zeroconf { } if info.is_addr_auto() { - let selected_intfs = self.selected_intfs(my_ip_interfaces(true)); + let selected_intfs = + self.selected_intfs(my_ip_interfaces_inner(true, self.include_apple_p2p)); for intf in selected_intfs { info.insert_ipaddr(&intf); } @@ -3922,6 +3940,7 @@ enum DaemonOption { MulticastLoopV4(bool), MulticastLoopV6(bool), AcceptUnsolicited(bool), + IncludeAppleP2P(bool), #[cfg(test)] TestDownInterface(String), #[cfg(test)] @@ -4044,13 +4063,29 @@ fn call_hostname_resolution_listener( /// Operational down interfaces are excluded. /// Loopback interfaces are excluded if `with_loopback` is false. fn my_ip_interfaces(with_loopback: bool) -> Vec { + my_ip_interfaces_inner(with_loopback, false) +} + +fn my_ip_interfaces_inner(with_loopback: bool, with_apple_p2p: bool) -> Vec { if_addrs::get_if_addrs() .unwrap_or_default() .into_iter() - .filter(|i| i.is_oper_up() && (!i.is_loopback() || with_loopback)) + .filter(|i| { + i.is_oper_up() + && !i.is_p2p() + && (!i.is_loopback() || with_loopback) + && (with_apple_p2p || !is_apple_p2p_by_name(&i.name)) + }) .collect() } +/// Checks if the interface name indicates it's an Apple peer-to-peer interface, +/// which should be ignored by default. +fn is_apple_p2p_by_name(name: &str) -> bool { + let p2p_prefixes = ["awdl", "llw"]; + p2p_prefixes.iter().any(|prefix| name.starts_with(prefix)) +} + /// Send an outgoing mDNS query or response, and returns the packet bytes. /// Returns empty vec if no valid interface address is found. fn send_dns_outgoing( @@ -4116,6 +4151,7 @@ fn send_dns_outgoing_impl( addr: if_addr.clone(), index: Some(if_index), oper_status: if_addrs::IfOperStatus::Down, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -4137,6 +4173,7 @@ fn send_dns_outgoing_impl( addr: if_addr.clone(), index: Some(if_index), oper_status: if_addrs::IfOperStatus::Down, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; diff --git a/src/service_info.rs b/src/service_info.rs index 00f86dba..a2dbfb17 100644 --- a/src/service_info.rs +++ b/src/service_info.rs @@ -1547,6 +1547,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -1561,6 +1562,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -1575,6 +1577,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -1589,6 +1592,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -1605,6 +1609,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -1619,6 +1624,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; @@ -1633,6 +1639,7 @@ mod tests { prefixlen: 16, }), oper_status: IfOperStatus::Up, + is_p2p: false, #[cfg(windows)] adapter_name: String::new(), }; diff --git a/tests/mdns_test.rs b/tests/mdns_test.rs index 21ab175d..d618f5f2 100644 --- a/tests/mdns_test.rs +++ b/tests/mdns_test.rs @@ -1173,6 +1173,11 @@ fn service_new_publish_after_browser() { daemon.shutdown().unwrap(); } +fn is_apple_p2p_by_name(name: &str) -> bool { + let p2p_prefixes = ["awdl", "llw"]; + p2p_prefixes.iter().any(|prefix| name.starts_with(prefix)) +} + fn my_ip_interfaces() -> Vec { // Use a random port for binding test. let test_port = fastrand::u16(8000u16..9000u16); @@ -1181,7 +1186,7 @@ fn my_ip_interfaces() -> Vec { .unwrap_or_default() .into_iter() .filter_map(|i| { - if i.is_loopback() { + if i.is_loopback() || i.is_p2p() || is_apple_p2p_by_name(&i.name) { None } else { match &i.addr {