Skip to content

Commit f3566ee

Browse files
committed
fix(discovery): normalize Bonjour names by stripping .local
Discovery now caches by service-instance label, but the announcer's choice of label is platform-dependent: macOS's `hostname::get()` returns the FQDN (`Foo.local`) while Linux's returns the short name (`omarchy`). Without normalization this works asymmetrically — a config of `omarchy.local` for a Linux peer wouldn't match the cached `omarchy` key. Add `normalize_mdns_name` (lower-case, drop trailing `.`, drop `.local` suffix) and apply it on both insert (start_browse) and lookup (`peer_primary_ip`, `should_attempt`, `connect_to_handle`). The `.local` domain is implied for everything mDNS-SD touches, so collapsing it on both sides is lossless and matches how `dns-sd` and Bonjour APIs treat instance labels in their wire form.
1 parent 5bb4464 commit f3566ee

2 files changed

Lines changed: 16 additions & 4 deletions

File tree

src/connect.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::client::ClientManager;
22
use crate::config::local_commit;
3-
use crate::discovery::PrimaryCache;
3+
use crate::discovery::{PrimaryCache, normalize_mdns_name};
44
use lan_mouse_ipc::{ClientHandle, DEFAULT_PORT};
55
use lan_mouse_proto::{MAX_EVENT_SIZE, ProtoEvent};
66
use local_channel::mpsc::{Receiver, Sender, channel};
@@ -282,7 +282,7 @@ impl LanMouseConnection {
282282
fn should_attempt(&self, handle: ClientHandle) -> bool {
283283
let ips = self.client_manager.get_ips(handle).unwrap_or_default();
284284
let primary = self.client_manager.get_hostname(handle).and_then(|h| {
285-
let key = h.strip_suffix('.').unwrap_or(&h).to_ascii_lowercase();
285+
let key = normalize_mdns_name(&h);
286286
self.primary_hints.borrow().get(&key).copied()
287287
});
288288
let sig = signature_of(&ips, primary);
@@ -326,7 +326,7 @@ async fn connect_to_handle(
326326
// so a healthy primary almost always wins regardless of
327327
// raw RTT ordering.
328328
let primary_ip = client_manager.get_hostname(handle).and_then(|h| {
329-
let key = h.strip_suffix('.').unwrap_or(&h).to_ascii_lowercase();
329+
let key = normalize_mdns_name(&h);
330330
primary_hints.borrow().get(&key).copied()
331331
});
332332
let preferred = primary_ip.map(|ip| SocketAddr::new(ip, port));

src/discovery.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ fn instance_from_fullname<'a>(fullname: &'a str, service_type: &str) -> &'a str
8181
fullname.strip_suffix(&suffix).unwrap_or(fullname)
8282
}
8383

84+
/// Canonicalize a Bonjour/mDNS-SD name for cache lookup. Lower-cases,
85+
/// drops a trailing FQDN dot, and drops the `.local` link-local
86+
/// suffix. The `.local` domain is implied for everything mDNS-SD
87+
/// touches, so callers shouldn't have to remember whether to include
88+
/// it — config that says `Foo.local`, an announcer's instance label
89+
/// `Foo`, and an SRV target `foo.local.` all collapse to `foo`.
90+
pub(crate) fn normalize_mdns_name(s: &str) -> String {
91+
let s = strip_trailing_dot(s);
92+
let s = s.strip_suffix(".local").unwrap_or(s);
93+
s.to_ascii_lowercase()
94+
}
95+
8496
/// Shared `peer_hostname -> primary_ipv4` map, populated by Discovery
8597
/// and read by the dialer (`connect_to_handle`). Owned by the dialer
8698
/// path so its references survive across discovery enable/disable
@@ -313,7 +325,7 @@ fn start_browse(
313325
};
314326
let instance =
315327
instance_from_fullname(resolved.get_fullname(), SERVICE_TYPE);
316-
let key = strip_trailing_dot(instance).to_ascii_lowercase();
328+
let key = normalize_mdns_name(instance);
317329
let target = strip_trailing_dot(resolved.get_hostname());
318330
log::info!(
319331
"mdns: peer instance={key} (target={target}) announces primary={ip} \

0 commit comments

Comments
 (0)