Reverse mDNS Lookup: Resolving Hostnames from IP Addresses Using Multicast DNS (mDNS)


1 views

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}