When working with devices on a local network that support Multicast DNS (mDNS), you might need to resolve hostnames from their IP addresses programmatically. While standard DNS provides PTR records for reverse lookups, mDNS requires a different approach since it operates peer-to-peer without a central DNS server.
The classic example shows how mDNS works in one direction:
ping computer.local
64 bytes from 192.168.0.52: icmp_seq=1 ttl=64 time=5.510 ms
But what if we start with just the IP (192.168.0.52) and need to discover the .local hostname?
Here are three reliable methods to perform reverse mDNS lookups:
1. Using avahi-resolve
On Linux systems with Avahi installed:
avahi-resolve -a 192.168.0.52
192.168.0.52 computer.local
2. Python with python-zeroconf
For programmatic solutions:
from zeroconf import Zeroconf
import socket
def reverse_mdns_lookup(ip):
zeroconf = Zeroconf()
try:
# mDNS uses reverse DNS format for queries
ptr_name = socket.inet_ntoa(socket.inet_aton(ip))[::-1] + ".in-addr.arpa"
info = zeroconf.get_service_info("_services._dns-sd._udp.local.", ptr_name)
return info.name if info else None
finally:
zeroconf.close()
print(reverse_mdns_lookup("192.168.0.52"))
3. Using dns-sd command line
On macOS or Linux with Bonjour installed:
dns-sd -Q 52.0.168.192.in-addr.arpa PTR
Several factors affect reverse mDNS resolution:
- The target device must be currently online and responding to mDNS queries
- Network firewalls must permit mDNS traffic (UDP port 5353)
- There may be a slight delay (2-5 seconds) for responses
- Some devices implement mDNS but don't respond to reverse queries
For applications that need to perform many lookups, consider implementing a cache:
from functools import lru_cache
@lru_cache(maxsize=128)
def cached_reverse_lookup(ip):
return reverse_mdns_lookup(ip)
If reverse lookups fail, try these diagnostic steps:
# Verify mDNS is working normally
avahi-browse -a
# Check for network filters
sudo tcpdump -i eth0 udp port 5353
# Test basic resolution
host 192.168.0.52
When working with networked devices that implement Multicast DNS (mDNS), we often face the reverse resolution problem: given an IP address, how can we retrieve its associated .local hostname? While standard DNS provides reverse lookup capabilities, mDNS requires different approaches.
For most programming environments, these are the go-to solutions:
- Python:
python-zeroconf
package - Node.js:
multicast-dns
module - Linux CLI:
avahi-resolve
command
Here's a complete Python script using zeroconf:
from zeroconf import Zeroconf
import socket
def reverse_mdns_lookup(ip):
zeroconf = Zeroconf()
try:
# Create a reverse lookup name format
reverse_name = "%s.in-addr.arpa." % ".".join(reversed(ip.split('.')))
# Query PTR record
answer = zeroconf.cache.entries_with_name(reverse_name)
for record in answer:
if record.type == 12: # PTR record type
return record.alias.lower().rstrip('.')
finally:
zeroconf.close()
# Usage example:
print(reverse_mdns_lookup("192.168.0.52")) # Outputs: computer.local
For quick checks without writing code:
# Using avahi (Linux)
avahi-resolve -a 192.168.0.52
# Using dns-sd (macOS)
dns-sd -q 52.0.168.192.in-addr.arpa
When implementing mDNS reverse lookups:
- Timeout handling is crucial - mDNS responses aren't guaranteed
- Some devices may not respond to reverse queries even if they support mDNS
- Network interfaces must support multicast traffic
- Consider implementing a cache for performance
If reverse lookup fails, you can try querying the host directly:
import socket
def direct_mdns_query(ip):
try:
hostname, _, _ = socket.gethostbyaddr(ip)
if hostname.endswith('.local'):
return hostname
except socket.herror:
return None
For scanning multiple IPs efficiently:
from concurrent.futures import ThreadPoolExecutor
def scan_network(ip_range):
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(reverse_mdns_lookup, ip_range))
return {ip: name for ip, name in zip(ip_range, results) if name}