A blocking connect() to mDNS-resolved IPv6 addresses fails with errno 0 on macOS 27, while a non-blocking connect to the same addresses succeeds.
1 | $ ssh -l terrence Aorus.local |
However,
1 | $ nslookup Aorus.local |
ssh IP address works fine:
1 | $ ssh -l terrence 192.168.2.229 |
ssh on hostname without domain also works:
1 | $ ssh -l terrence Aorus |
ssh via proxy also works:
1 | $ ssh -o ProxyCommand='nc %h %p' -l terrence Aorus.local |
Why they differ: nslookup queries router’s DNS server (192.168.2.1) directly and returns the IPv4 address. ssh doesn’t use DNS for .local names. It goes through the macOS system resolver, which uses mDNS/Bonjour. That path can return a different answer, for example an IPv6 link-local address (fe80::…) advertised by Aorus (avahi, it’s Linux).
And local router also resolves a plain Aorus name, since that goes through unicast DNS and avoids mDNS entirely.
dscacheutil queries on Aorus.local only IPv6 no IPv4 addresses returned:
1 | $ dscacheutil -q host -a name Aorus.local |
Connect via IPv6 works fine:
1 | $ ping6 -c2 2401:d002: ... |
To monitor TCP/IP traffic:
1 | $ sudo tcpdump -i en1 -n 'ip6 and tcp port 22' |
Finally, try ConnectTimeout in ssh works:
1 | $ ssh -o ConnectTimeout=5 -l terrence Aorus.local |
Add the following configuration in .ssh/config file:
1 | Host Aorus.local |
ConnectTimeout changes how ssh opens the socket. With no timeout set, ssh calls a plain blocking connect(). With a timeout, it switches the socket to non-blocking mode and waits for it with poll(). nc connects the non-blocking way too, which is why it worked all along.
So the evidence points to the blocking connect() to these mDNS-resolved IPv6 addresses failing on current macOS build, while the non-blocking path works. This could be a bug in this macOS build’s handling of mDNS-resolved scoped addresses, or something in the Local Network privacy layer.
Only the hostname path failed, because Aorus.local resolves to IPv6 addresses with an interface scope, and literals and the IPv4 name don’t hit that.
ConnectTimeout is harmless, since it just makes unreachable hosts fail after 10 seconds instead of the system default. It also covers scp, rsync and git over ssh.