lan-mouse icon indicating copy to clipboard operation
lan-mouse copied to clipboard

Feature: resolution of mDNS host name (.local)

Open DrYak opened this issue 11 months ago • 6 comments

Would there be an easy way for lan-mouse to resolve mDNS (avahi, etc.) host names so machines can easily find each other on the network?

Request:

Currently, it seems that lan-mouse treats them as a regular host name, adds the DNS domain and performs a normal look-up:

…
[left]
# hostname1
hostname = "nereid.local"
…

leads to:

2025-02-12T10:37:31Z WARN  lan_mouse::service] could not resolve nereid.local: no record found for Query { name: Name("nereid.local.ethz.ch."), query_type: AAAA, query_class: IN }

Adding a dot at least avoids appending the domain, but still performs normal DNS look-up (which fails):

…
[left]
# hostname1
hostname = "nereid.local."
…

leads to:

2025-02-12T10:37:31Z WARN  lan_mouse::service] could not resolve nereid.local: no record found for Query { name: Name("nereid.local."), query_type: AAAA, query_class: IN }

Is there some rust resolver library which could easily abstract that? (e.g.: something like the GNU C Library which uses /etc/nsswitch.conf)

Context:

my university uses horribly arcane automatic naming, based on the abstract connection to the wired 802.1x router to name the machines (today that netbook is bsse-bs-dock-128-117.ethz.ch.). Finding each-other based on mDNS is much more convenient, but currently I need to copy-paste the output of avahi-resolve-host-name -n nereid.local into an ip = [ … ] option of the config.toml

DrYak avatar Feb 12 '25 10:02 DrYak

I really don't know too much about mDNS but I guess there is this crate which seems fairly popular: https://crates.io/crates/mdns

I will have to read up on the whole mDNS / avahi stuff though before I do something dumb.

feschber avatar Feb 12 '25 13:02 feschber

I would say you don't (necessarily⁺) need to handle the whole mDNS support inside lan-mouse yourself. (i.e.: You don't need to advertise services on the local net and auto-discover other clients and servers -- which is part of the purposes covered by mDNS. See the automatic discovery of stuff like AirPlay, CUPS and AirPrint, DLNA media streaming, etc. E.g. the demo code on that crate tries to auto-discover all Chromcast devices currently on the local network) Among other you don't need to run your own multicast server to advertise whatever.

What you just need is the "resolve names" part: just being able to find the IP address that corresponds to a .local domain.

I think this would better cover the needs: https://crates.io/crates/mdns-resolver

This crate performs local DNS lookups in a similar fashion as avahi-resolve -n; in other words, it translates Bonjour-style hostnames (e.g. foo.local) to an IP address.

and specially this part:

Most resolvers (including simple-mdns) are intended to browse or query services by type rather than name. This crate is intended to reliably discover the IP for a given hostname in the same fashion that a desktop device with Avahi or another similar local resolver would resolve hosts under the virtual .local domain.

The demo binary is exactly what lan-mouse would need to do: https://github.com/timothyb89/mdns-resolver/blob/master/src/bin/mdns-query.rs#L32

…
let resolver = MdnsResolver::new().await?;
  let query = match env::args().skip(1).next() {
    Some(query) => query,
    None => {
      error!("usage: {} hostname", env::args().next().unwrap_or("mdns-query".into()));
      process::exit(1);
    }
  };

  let res = match resolver.query_timeout(query, Duration::from_millis(1000)).await {
    Ok(result) => result,
    Err(e) => {
      error!("could not resolve query: {}", e);
      process::exit(1);
    }
  };

  let packet = res.to_packet()?;
  for answer in packet.answers {
    let addr = match answer.rdata {
      simple_dns::rdata::RData::A(rec) => IpAddr::V4(Ipv4Addr::from(rec.address)),
      simple_dns::rdata::RData::AAAA(rec) => IpAddr::V6(Ipv6Addr::from(rec.address)),
      _ => continue,
    };
    println!("{} = {}", answer.name, addr);
  }
…

I just don't know how good this is as a package. (I'm not fluent in Rust)


⁺ : well, you could go the whole she-bang and implement a full mDNS server, and do automatic service discovery, and allow instances of lan-mouse on the same network to autodiscover each other, and e.g. rely on the encryption key to trust each other and put on the correct border. (i.e.: you completely drop machine names and ip addresses out of the configuration, you just have entries using public key and it's up to the multicast system to find if somewhere on the network there's a machine with authorised matching keys). But that's probably way beyond the scope of lan-mouse 1.0.

DrYak avatar Feb 18 '25 11:02 DrYak

.local worked just fine for me. Maybe your system is not configured to resolve .local correctly? Try host nereid.local, if that doesn't work well...

[2025-02-27T22:41:48Z INFO  lan_mouse::dns] resolving (0) `foo.local` ...
[2025-02-27T22:41:59Z INFO  lan_mouse::dns] foo.local: adding ip 10.0.0.4

tv42 avatar Feb 27 '25 22:02 tv42

Maybe your system is not configured to resolve .local correctly?

Depends on how you define 'correctly'. In my case, I have setup mdns in /etc/nsswitch.conf, so software that relies simply on libc for name resolution will successfully resolve the name:

(deck@steamdeck ~)$ grep '^hosts' /etc/nsswitch.conf 
hosts: mymachines mdns_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname dns
(deck@steamdeck ~)$ ping -c 1 nereid.local
PING nereid.local (2a00:1028:83aa:98e6:8b41:f671:a7bf:68e4%2) 56 data bytes
64 bytes from nereid-1.home (2a00:1028:83aa:98e6:8b41:f671:a7bf:68e4): icmp_seq=1 ttl=64 time=621 ms

--- nereid.local ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 620.505/620.505/620.505/0.000 ms

BUT /etc/resolv.conf merely point to the modem/router's internal DNS and that one doesn't resolve mDNS/avahi host names. So software that spefically only talks to DNS servers (like apparently your test, or like drill / dig) will not resolve those addrress.

I know it's possible to use a local DNS resolver (dnsmasq, systemd-resolved, etc.) that can do a lot more stuff (forward to an external DNS, cache answers, query other sources), it just happens that these specific machines haven't one setup currently.

DrYak avatar Jul 25 '25 14:07 DrYak

quickly setting up systemd-resolved because it's super trivial:

[deck@steamdeck ~]$ sudo resolvectl mdns wlan0 yes
Setting mDNS support level "yes" for "wlan0", but the global support level is "no".
[deck@steamdeck ~]$ sudo mkdir /etc/systemd/resolved.conf.d
[deck@steamdeck ~]$ sudo nano /etc/systemd/resolved.conf.d/99-multicast.conf
[Resolve]
MulticastDNS=yes
[deck@steamdeck ~]$ sudo systemctl reload systemd-resolved.service 
[deck@steamdeck ~]$ sudo resolvectl mdns
Global: yes
Link 2 (wlan0): yes
Link 5 (enp4s0f3u1u4c2): yes
[deck@steamdeck ~]$ resolvectl query nereid.local
nereid.local: 2a00:1028:83aa:98e6:8b41:f671:a7bf:68e4 -- link: wlan0

-- Information acquired via protocol mDNS/IPv6 in 350.0ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network
[deck@steamdeck ~]$ # Because SteamOS is a immutable distro
[deck@steamdeck ~]$ sudo nano /etc/atomic-update.conf.d/custom-dryak.conf 
/etc/systemd/resolved.conf.d/99-multicast.conf
(deck@steamdeck ~]$ ~/bin/lan-mouse
[2025-07-25T14:59:53Z INFO  lan_mouse] using config: "/home/deck/.config/lan-mouse/config.toml"
[2025-07-25T14:59:53Z INFO  lan_mouse] Press [KeyLeftCtrl, KeyLeftShift, KeyLeftMeta, KeyLeftAlt] to release the mouse
[2025-07-25T14:59:53Z INFO  lan_mouse::service] activated client 1 (left)

so yes indeed, the limitations are that lan-mouse only ever queries the DNS server, so the only way to get other type to names to resolve is to use a local resolver (dnsmasq, systemd-resolved, etc.) that has been configured to forward .local. domain to mDNS instead of upstream DNS server.

DrYak avatar Jul 25 '25 15:07 DrYak

Note that as per RFC 6762, the above behaviour violates the Multicast Standard:

Any DNS query for a name ending with ".local." MUST be sent to the mDNS IPv4 link-local multicast address 224.0.0.251 (or its IPv6 equivalent FF02::FB).

[!NOTE] RFC 2119 defines MUST as an absolute requirement

BUT some implementations are limited, e.g., libc musl used in Alpine Linux only supports classic DNS name resolution (unlike glibc) and thus needs to rely on tricks such as avahi2dns.

This might be the reason why software like systemd-resolved decided to implement mDNS to help such more limited environments. And in turn, given the popularity of that implementation, it might explain why software libraries implement mDNS 'the wrong way around' (by querying DNS instead of directly querying mDNS) because mDNS-enabled sytemd-resolved.conf are widespread.

So the fact that it works sometime in lan-mouse is just a happy coincidence of a very widespread hack.

DrYak avatar Jul 25 '25 15:07 DrYak