[PR #423] feat(discovery): mDNS-SD primary-IP hints for service-order-aware dialing #415

Open
opened 2026-05-05 22:18:36 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/feschber/lan-mouse/pull/423
Author: @jondkinney
Created: 5/6/2026
Status: 🔄 Open

Base: mainHead: split/04-mdns


📝 Commits (10+)

  • 31298f2 feat: cross-platform wall-press auto-release fallback
  • bffbef0 proto: add Bounds(width, height) event variant
  • 2219f45 feat(emulation): display_bounds + warp_cursor for all backends
  • 5de5016 feat(emulation): send Bounds + warp cursor on Enter
  • 68fab55 feat(capture): cache peer bounds and use as wall-press upper clamp
  • 944e758 ui: wrap window content in GtkScrolledWindow
  • 56f7730 ui: rename Auto-Release group to scope it to outgoing capture
  • cd63f5d fix: preserve cross-axis cursor position across machine transitions
  • 2571b47 fix(capture/layer_shell): report screen-space cursor position on Enter
  • cbe2ec1 fix(capture): warp host cursor to guest position on release

📊 Changes

37 files changed (+3476 additions, -394 deletions)

View changed files

📝 Cargo.lock (+494 -204)
📝 Cargo.toml (+5 -1)
📝 input-capture/Cargo.toml (+1 -0)
📝 input-capture/src/dummy.rs (+2 -2)
📝 input-capture/src/layer_shell.rs (+67 -17)
📝 input-capture/src/lib.rs (+579 -11)
📝 input-capture/src/libei.rs (+5 -2)
📝 input-capture/src/macos.rs (+152 -13)
📝 input-capture/src/windows.rs (+1 -1)
📝 input-capture/src/windows/event_thread.rs (+64 -12)
📝 input-capture/src/x11.rs (+1 -1)
📝 input-emulation/src/lib.rs (+36 -0)
📝 input-emulation/src/libei.rs (+41 -2)
📝 input-emulation/src/macos.rs (+34 -1)
📝 input-emulation/src/windows.rs (+24 -1)
📝 input-emulation/src/wlroots.rs (+190 -1)
📝 input-emulation/src/x11.rs (+27 -0)
📝 lan-mouse-gtk/resources/window.ui (+83 -5)
📝 lan-mouse-gtk/src/client_object.rs (+10 -0)
📝 lan-mouse-gtk/src/client_object/imp.rs (+1 -0)

...and 17 more files

📄 Description

Summary

mDNS-SD service-order discovery — even with a multi-homed listener, the dialer still has to choose which of the peer's IPs to dial first, and plain hostname resolution returns every interface's IP without ranking. connect_any's parallel race picks whichever DTLS handshake completes first, which is RTT-roughly-correct but not always what the user wanted. The classic symptom: Wi-Fi wins the race even when the user has Ethernet ranked higher in macOS's service order, leading to a stuttery session over Wi-Fi while a healthy wired path sits idle.

Each lan-mouse instance now publishes a _lan-mouse._udp.local. Bonjour service whose TXT record carries primary=<ipv4>, where <ipv4> is the IP of the interface that owns the default route — which on macOS reflects service order, on Linux the lowest-metric default route, on Windows whatever GetBestRoute2 selects. The dialer continuously browses the same service type and caches peer_hostname → primary_ipv4 in a Rc<RefCell<HashMap>> shared with LanMouseConnection.

connect_any extended with happy-eyeballs head-start: if a preferred address is known, dial it alone for 200ms before joining the rest of the candidate list to the race. A healthy preferred path virtually always wins; a broken one only delays connect by 200ms before fallbacks kick in. (Cf. RFC 8305 IPv6→IPv4 fallback delay.)

Subsystem gated by a new mdns_discovery config flag (default true) and a corresponding GUI switch under a new "Network Discovery" preferences group. Toggling off unregisters the service, aborts the browse task, and shuts the daemon, but preserves the primary_cache so already-known hints stay queryable until overwritten — useful on networks where mDNS multicast (224.0.0.251) is firewalled. A 30-second discovery_refresh_tick re-publishes the TXT record so it stays accurate when the OS-preferred interface changes (e.g. user toggles Wi-Fi off and Ethernet takes over).

New deps: mdns-sd (cross-platform mDNS responder, doesn't piggyback on system Avahi/Bonjour), netdev (default-route lookup), hostname (local hostname for the service instance name).

Falls back gracefully when ServiceDaemon::new fails (multicast group locked / no perms), no interface owns the default route, or the peer isn't announcing (old version or discovery disabled there) — the dialer just sees preferred = None and the existing connect_any race runs unchanged.

Test plan

  • Multi-homed Mac (Wi-Fi + Ethernet on same subnet): Linux dialer consistently selects the Ethernet path even when Wi-Fi wins a raw RTT race
  • Disable mDNS via the GUI switch on one peer — that peer's record disappears; the other peer's dialer falls back to non-preferred connection
  • Toggle the Mac's Wi-Fi off mid-session — within 30s the discovery refresh tick re-publishes with the new primary IP
  • Old peer (no _lan-mouse._udp.local. advertised) — dialer sees preferred = None and falls back to existing race behavior

Split out from #418, the umbrella PR collecting ~10 independent feature areas. This PR is the mDNS-SD discovery subset and depends on the multi-homed-listener PR. See #418 for the full picture.

Stack overview

These PRs are split out from #418 and stack in this order:

  1. #420 — cursor sync + wall-press + host-lock + slider/UI
  2. #421 — peer version exchange
  3. #422 — hostname resolver + multi-homed DTLS listener
  4. #423 — mDNS-SD service-order discovery
  5. #424 — macOS QoL + UI polish
  6. #425 — scroll forwarding
  7. #426 — GUI singleton

Each PR's branch builds on the previous one, so until earlier PRs are merged the cumulative diff against main includes all preceding work. Reviewing in order is easiest.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/feschber/lan-mouse/pull/423 **Author:** [@jondkinney](https://github.com/jondkinney) **Created:** 5/6/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `split/04-mdns` --- ### 📝 Commits (10+) - [`31298f2`](https://github.com/feschber/lan-mouse/commit/31298f29577e06d4fda36188f5721cd93123e17a) feat: cross-platform wall-press auto-release fallback - [`bffbef0`](https://github.com/feschber/lan-mouse/commit/bffbef0875257c24227e6599e64b4e7a0030e1b2) proto: add Bounds(width, height) event variant - [`2219f45`](https://github.com/feschber/lan-mouse/commit/2219f45a1c37ac9f324728470ea7900eb804c2f6) feat(emulation): display_bounds + warp_cursor for all backends - [`5de5016`](https://github.com/feschber/lan-mouse/commit/5de501637d1e6a5e486c9a3799d8c069ae0d5184) feat(emulation): send Bounds + warp cursor on Enter - [`68fab55`](https://github.com/feschber/lan-mouse/commit/68fab558b340a224e075b842f6182b2c7d6646a3) feat(capture): cache peer bounds and use as wall-press upper clamp - [`944e758`](https://github.com/feschber/lan-mouse/commit/944e7582b783252e07c895b16c5440253b8ecbfe) ui: wrap window content in GtkScrolledWindow - [`56f7730`](https://github.com/feschber/lan-mouse/commit/56f7730164171cbb3dfb0176ca2eae7e314460c6) ui: rename Auto-Release group to scope it to outgoing capture - [`cd63f5d`](https://github.com/feschber/lan-mouse/commit/cd63f5d42875bd27f4724eb9fe3ce787e5725c6c) fix: preserve cross-axis cursor position across machine transitions - [`2571b47`](https://github.com/feschber/lan-mouse/commit/2571b47074da9f68e56501eefbb8ea959b67f5a7) fix(capture/layer_shell): report screen-space cursor position on Enter - [`cbe2ec1`](https://github.com/feschber/lan-mouse/commit/cbe2ec13030fa565f1e562ecf5e09cc5d2a8c637) fix(capture): warp host cursor to guest position on release ### 📊 Changes **37 files changed** (+3476 additions, -394 deletions) <details> <summary>View changed files</summary> 📝 `Cargo.lock` (+494 -204) 📝 `Cargo.toml` (+5 -1) 📝 `input-capture/Cargo.toml` (+1 -0) 📝 `input-capture/src/dummy.rs` (+2 -2) 📝 `input-capture/src/layer_shell.rs` (+67 -17) 📝 `input-capture/src/lib.rs` (+579 -11) 📝 `input-capture/src/libei.rs` (+5 -2) 📝 `input-capture/src/macos.rs` (+152 -13) 📝 `input-capture/src/windows.rs` (+1 -1) 📝 `input-capture/src/windows/event_thread.rs` (+64 -12) 📝 `input-capture/src/x11.rs` (+1 -1) 📝 `input-emulation/src/lib.rs` (+36 -0) 📝 `input-emulation/src/libei.rs` (+41 -2) 📝 `input-emulation/src/macos.rs` (+34 -1) 📝 `input-emulation/src/windows.rs` (+24 -1) 📝 `input-emulation/src/wlroots.rs` (+190 -1) 📝 `input-emulation/src/x11.rs` (+27 -0) 📝 `lan-mouse-gtk/resources/window.ui` (+83 -5) 📝 `lan-mouse-gtk/src/client_object.rs` (+10 -0) 📝 `lan-mouse-gtk/src/client_object/imp.rs` (+1 -0) _...and 17 more files_ </details> ### 📄 Description ## Summary mDNS-SD service-order discovery — even with a multi-homed listener, the **dialer** still has to choose which of the peer's IPs to dial first, and plain hostname resolution returns every interface's IP without ranking. `connect_any`'s parallel race picks whichever DTLS handshake completes first, which is RTT-roughly-correct but not always what the user wanted. The classic symptom: Wi-Fi wins the race even when the user has Ethernet ranked higher in macOS's service order, leading to a stuttery session over Wi-Fi while a healthy wired path sits idle. Each lan-mouse instance now publishes a `_lan-mouse._udp.local.` Bonjour service whose TXT record carries `primary=<ipv4>`, where `<ipv4>` is the IP of the interface that owns the default route — which on macOS reflects service order, on Linux the lowest-metric default route, on Windows whatever `GetBestRoute2` selects. The dialer continuously browses the same service type and caches `peer_hostname → primary_ipv4` in a `Rc<RefCell<HashMap>>` shared with `LanMouseConnection`. `connect_any` extended with **happy-eyeballs head-start**: if a preferred address is known, dial it alone for 200ms before joining the rest of the candidate list to the race. A healthy preferred path virtually always wins; a broken one only delays connect by 200ms before fallbacks kick in. (Cf. RFC 8305 IPv6→IPv4 fallback delay.) Subsystem gated by a new `mdns_discovery` config flag (default true) and a corresponding GUI switch under a new "Network Discovery" preferences group. Toggling off unregisters the service, aborts the browse task, and shuts the daemon, but preserves the `primary_cache` so already-known hints stay queryable until overwritten — useful on networks where mDNS multicast (`224.0.0.251`) is firewalled. A 30-second `discovery_refresh_tick` re-publishes the TXT record so it stays accurate when the OS-preferred interface changes (e.g. user toggles Wi-Fi off and Ethernet takes over). New deps: `mdns-sd` (cross-platform mDNS responder, doesn't piggyback on system Avahi/Bonjour), `netdev` (default-route lookup), `hostname` (local hostname for the service instance name). Falls back gracefully when `ServiceDaemon::new` fails (multicast group locked / no perms), no interface owns the default route, or the peer isn't announcing (old version or discovery disabled there) — the dialer just sees `preferred = None` and the existing `connect_any` race runs unchanged. ## Test plan - [x] Multi-homed Mac (Wi-Fi + Ethernet on same subnet): Linux dialer consistently selects the Ethernet path even when Wi-Fi wins a raw RTT race - [x] Disable mDNS via the GUI switch on one peer — that peer's record disappears; the other peer's dialer falls back to non-preferred connection - [x] Toggle the Mac's Wi-Fi off mid-session — within 30s the discovery refresh tick re-publishes with the new primary IP - [x] Old peer (no `_lan-mouse._udp.local.` advertised) — dialer sees `preferred = None` and falls back to existing race behavior --- Split out from #418, the umbrella PR collecting ~10 independent feature areas. This PR is the mDNS-SD discovery subset and depends on the multi-homed-listener PR. See #418 for the full picture. ## Stack overview These PRs are split out from #418 and stack in this order: 1. #420 — cursor sync + wall-press + host-lock + slider/UI 2. #421 — peer version exchange 3. #422 — hostname resolver + multi-homed DTLS listener 4. #423 — mDNS-SD service-order discovery 5. #424 — macOS QoL + UI polish 6. #425 — scroll forwarding 7. #426 — GUI singleton Each PR's branch builds on the previous one, so until earlier PRs are merged the cumulative diff against `main` includes all preceding work. Reviewing in order is easiest. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
gitea-mirror added the
pull-request
label 2026-05-05 22:18:36 -06:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/lan-mouse#415
No description provided.