Skip to content

Commit a1c1f2a

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 322c0af commit a1c1f2a

2 files changed

Lines changed: 19 additions & 7 deletions

File tree

src/connect.rs

Lines changed: 4 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};
@@ -277,6 +277,7 @@ impl LanMouseConnection {
277277
/// - the candidate-set signature has changed since the last
278278
/// attempt (new IP from DNS, or new mDNS primary), or
279279
/// - the recorded backoff has elapsed.
280+
///
280281
/// Otherwise returns false; the caller treats this as "still in
281282
/// cooldown, keep returning NotConnected silently."
282283
fn should_attempt(&self, handle: ClientHandle) -> bool {
@@ -285,7 +286,7 @@ impl LanMouseConnection {
285286
.client_manager
286287
.get_hostname(handle)
287288
.and_then(|h| {
288-
let key = h.strip_suffix('.').unwrap_or(&h).to_ascii_lowercase();
289+
let key = normalize_mdns_name(&h);
289290
self.primary_hints.borrow().get(&key).copied()
290291
});
291292
let sig = signature_of(&ips, primary);
@@ -329,7 +330,7 @@ async fn connect_to_handle(
329330
// so a healthy primary almost always wins regardless of
330331
// raw RTT ordering.
331332
let primary_ip = client_manager.get_hostname(handle).and_then(|h| {
332-
let key = h.strip_suffix('.').unwrap_or(&h).to_ascii_lowercase();
333+
let key = normalize_mdns_name(&h);
333334
primary_hints.borrow().get(&key).copied()
334335
});
335336
let preferred = primary_ip.map(|ip| SocketAddr::new(ip, port));

src/discovery.rs

Lines changed: 15 additions & 4 deletions
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
@@ -271,7 +283,7 @@ impl Discovery {
271283
if self.daemon.is_none() {
272284
return None;
273285
}
274-
let key = strip_trailing_dot(hostname).to_ascii_lowercase();
286+
let key = normalize_mdns_name(hostname);
275287
self.primary_cache.borrow().get(&key).copied()
276288
}
277289
}
@@ -311,9 +323,8 @@ fn start_browse(
311323
);
312324
continue;
313325
};
314-
let instance =
315-
instance_from_fullname(resolved.get_fullname(), SERVICE_TYPE);
316-
let key = strip_trailing_dot(instance).to_ascii_lowercase();
326+
let instance = instance_from_fullname(resolved.get_fullname(), SERVICE_TYPE);
327+
let key = normalize_mdns_name(instance);
317328
let target = strip_trailing_dot(resolved.get_hostname());
318329
log::info!(
319330
"mdns: peer instance={key} (target={target}) announces primary={ip} \

0 commit comments

Comments
 (0)