XiaTools

Ruby Resolv Library: Building a Quick IP Hostname Scanner

Updated 11 Oct 2026

Building a custom IP hostname scanner in Ruby using the built-in Resolv library is a reliable way to perform bulk reverse DNS lookups without relying on external gems. By leveraging Ruby's standard library, you can query pointer (PTR) records efficiently to map IP addresses back to their fully qualified domain names. This approach is ideal for network auditing, security log analysis, and system administration tasks where speed and portability matter.

Before you start scanning large subnets, you can test individual pointer records using the Reverse DNS Lookup tool on XiaTools to instantly check what nameserver responses look like for your target addresses.

Understanding Ruby Resolv and PTR Records

Reverse DNS lookup translates an IP address into a human-readable hostname by querying PTR records stored in the Domain Name System (DNS). While standard lookups (A or AAAA records) map domains to IPs, reverse lookups require querying an inverted IP address structure appended with in-addr.arpa for IPv4 or ip6.arpa for IPv6.

The Ruby standard library includes the resolv library, which provides a pure Ruby DNS resolver capable of handling these queries asynchronously or synchronously. Unlike system-dependent tools, Resolv works identically across operating systems, making your scanning scripts fully cross-platform.

Core Classes in Resolv

  • Resolv::DNS: The primary interface for making DNS queries.
  • Resolv::IPv4 and Resolv::IPv6: Utility classes for validating and manipulating IP addresses.
  • Resolv::DNS::Resource::IN::PTR: The specific DNS resource class representing pointer records.

Setting Up Your Environment

No external gems are required to build a basic hostname scanner in Ruby. You only need a standard Ruby installation (version 2.5 or newer recommended) and a terminal.

Create a new file named scanner.rb and require the necessary modules:

require 'resolv'
require 'ipaddr'

The ipaddr library helps generate ranges of IP addresses easily, while resolv performs the actual network queries.

Building the Ruby Resolv Getname Script

Let's write a practical script that takes an IP range, iterates through each address, and attempts to resolve its hostname using Resolv.getname.

Basic Synchronous Scanner

Here is a complete, straightforward script that scans an IPv4 subnet using documentation addresses (192.0.2.0/24) as an example:

require 'resolv'
require 'ipaddr'

# Define the target subnet using documentation IP range
subnet = '192.0.2.0/28'

puts "Scanning subnet: #{subnet}\n"

IPAddr.new(subnet).to_range.each do |ip|
  ip_str = ip.to_s
  begin
    # Use Resolv.getname to fetch the hostname
    hostname = Resolv.getname(ip_str)
    puts "[+] #{ip_str} -> #{hostname}"
  rescue Resolv::ResolvError
    # Handle cases where no PTR record exists
    puts "[-] #{ip_str} -> No hostname found"
  rescue StandardError => e
    puts "[!] #{ip_str} -> Error: #{e.message}
  end
end

Run the script from your terminal using:

ruby scanner.rb

Sample Output

Scanning subnet: 192.0.2.0/28

[-] 192.0.2.0 -> No hostname found
[+] 192.0.2.1 -> router.example.com
[+] 192.0.2.2 -> ns1.example.com
[-] 192.0.2.3 -> No hostname found

Optimizing Performance with Concurrency

Synchronous DNS lookups can be exceptionally slow because your script waits for a timeout on unresponsive IP addresses before moving to the next one. To scan large networks efficiently, you must introduce concurrency using Ruby threads or fiber-based asynchronous libraries.

Here is an updated version of the script utilizing threads to query multiple IP addresses simultaneously:

require 'resolv'
require 'ipaddr'

subnet = '192.0.2.0/24'
threads = []

puts "Starting concurrent scan on #{subnet}...\n"

IPAddr.new(subnet).to_range.each do |ip|
  threads << Thread.new(ip.to_s) do |ip_str|
    begin
      # Timeout configuration can be added via Resolv::DNS client setup
      hostname = Resolv.getname(ip_str)
      puts "[+] #{ip_str} => #{hostname}"
    rescue Resolv::ResolvError
      # Quietly ignore unresolvable IPs in bulk scans
    end
  end
end

# Wait for all threads to complete
threads.each(&:join)
puts "\nScan complete."

Advanced Customization: Setting DNS Timeouts

By default, Resolv.getname uses system resolvers and default timeouts. If you are scanning unresponsive IP blocks, your script can hang for several seconds per IP. You can override this behavior by instantiating a custom Resolv::DNS client with explicit timeout parameters.

require 'resolv'

# Configure a custom resolver with specific nameservers and timeouts
resolver = Resolv::DNS.new(
  nameserver: ['8.8.8.8', '1.1.1.1'],
  search: ['example.com'],
  ndots: 1
)

ip = '192.0.2.1'

begin
  # Using the resource query directly allows finer control
  ptr = Resolv::DNS::Resource::IN::PTR.create(ip)
  # Alternatively, use getname on the custom resolver instance
  hostname = resolver.getname(ip)
  puts "Hostname for #{ip}: #{hostname}"
rescue Resolv::ResolvError
  puts "Resolution failed for #{ip}"
end

Comparison: Standard Resolv vs. External DNS Gems

Feature Ruby Standard Resolv External C-Based Gems Pure Ruby Asynchronous Gems
Dependencies None (Built-in) Requires compiler / C extensions Requires gem installation
Portability Excellent (Works everywhere) Can fail on restricted environments Good, depends on event loop
Speed Moderate (Threaded) Very Fast Extremely Fast
Complexity Low Medium High

Common Mistakes and How to Fix Them

  • Not handling exceptions: Failing to catch Resolv::ResolvError will crash your script the moment an IP address lacks a PTR record. Always wrap lookup calls in begin...rescue blocks.
  • Ignoring network timeouts: Synchronous lookups without explicit timeouts will stall your application indefinitely if a target nameserver drops packets. Configure custom timeouts when scanning public or untrusted ranges.
  • Hitting rate limits: Querying thousands of addresses too quickly can trigger rate-limiting or IP bans on public resolvers like Google or Cloudflare. Use local caching nameservers or throttle your thread pool.
  • Incorrect IP range parsing: Passing strings directly to iteration loops without IPAddr often leads to malformed octet sequences. Always parse subnets using IPAddr.new(subnet).to_range.

Quick Checklist for Your Scanner

  • Require both resolv and ipaddr libraries.
  • Wrap all Resolv.getname calls in exception handlers.
  • Implement multi-threading or asynchronous processing for subnets larger than /28.
  • Set custom DNS server addresses and timeouts if default system resolvers are slow.
  • Verify pointer records manually using online tools before running large automation scripts.

Building a ruby resolv getname script gives you complete control over your network discovery tasks without external package dependencies. Whether you are auditing a private VLAN or analyzing server logs, combining Resolv with IPAddr provides a robust, lightweight solution for automated reverse DNS mapping.

Frequently asked questions

What is the difference between Resolv.getname and Resolv.getaddress?

Resolv.getname performs a reverse DNS lookup, taking an IP address string and returning its associated hostname via PTR records. Conversely, Resolv.getaddress performs a forward DNS lookup, taking a hostname and returning its corresponding IP address.

Why does my Ruby Resolv script run very slowly on large IP ranges?

By default, synchronous DNS queries wait for a timeout response from the nameserver before moving to the next address when an IP has no PTR record. To fix this, implement multi-threading or configure a custom DNS client instance with shorter timeout thresholds.

Can I use Ruby Resolv to scan IPv6 subnets as well as IPv4?

Yes, the Resolv library and the IPAddr class both natively support IPv6 addresses. You can pass an IPv6 CIDR block like 2001:db8::/64 to your range generator, and Resolv will construct the correct ip6.arpa pointer queries automatically.

How do I specify a custom DNS server instead of using the local system resolver?

You can instantiate a dedicated resolver object by passing nameserver IP addresses directly into the initialization method, such as Resolv::DNS.new(nameserver: ['192.0.2.53']). This forces your script to query your specified nameserver rather than relying on /etc/resolv.conf.

What exception should I rescue when an IP address does not have a PTR record?

You should rescue Resolv::ResolvError, which is the base exception raised by the Resolv library when a DNS resource cannot be found or resolved. Catching this specific error prevents your scanning script from crashing on unassigned IP addresses.

Related articles

Free tools