Building a Unified Passive DNS Client: How passivedns-client Abstracts Six Intelligence Providers
Hook
Security researchers waste hours manually querying six different passive DNS APIs to track a single piece of malware infrastructure. What if you could query them all with one command?
Context
Passive DNS databases are the security industry's time machines—they record historical DNS resolution data that reveals which domains pointed to which IP addresses over time. When investigating malicious infrastructure, threat actors, or phishing campaigns, analysts need to pivot through this data: find all domains that resolved to a suspicious IP, then find all IPs those domains pointed to, recursively mapping an attacker's infrastructure. The problem? The passive DNS landscape is fragmented across commercial providers (Farsight DNSDB, RiskIQ, VirusTotal) and open-source alternatives (CIRCL, 360.cn), each with different APIs, authentication schemes, response formats, and rate limits.
Before passivedns-client, security teams either built custom integrations for each provider or manually queried multiple web interfaces during investigations. This wasn't just inefficient—it created vendor lock-in and made it nearly impossible to cross-reference data or switch providers. Chris Lee's solution treats passive DNS providers as interchangeable data sources behind a unified interface, similar to how database ORMs abstract different SQL engines. The result is a Ruby gem that lets you query six providers with identical syntax and get normalized results, complete with graph visualization and recursive pivoting capabilities.
Technical Insight
The architecture revolves around an elegant adapter pattern. Each passive DNS provider inherits from a PassiveDB base class and implements a single method: lookup(item, type=nil). The library handles authentication, rate limiting, result normalization, and output formatting at a higher level. Here's what a provider implementation looks like:
class CirclProvider < PassiveDB
def lookup(item, type=nil)
# Construct request to CIRCL's API
url = "https://www.circl.lu/pdns/query/#{item}"
response = RestClient.get(url, {accept: :json})
# Parse and normalize to PDNSResult objects
JSON.parse(response).map do |record|
PDNSResult.new(
record['rrname'],
record['rdata'],
record['rrtype'],
record['time_first'],
record['time_last']
)
end
end
end
The PDNSResult class acts as a canonical data structure that all providers must populate. This abstraction means client code never cares whether data came from CIRCL, DNSDB, or VirusTotal—it's all the same shape. The library's state manager adds sophisticated capabilities on top of this simple interface.
The SQLite-backed state persistence is particularly clever. When you run recursive queries with --state=investigation.db, the tool stores every query and result in a local database. This serves three purposes: it enables resuming long-running investigations, prevents duplicate queries when you pivot through the same infrastructure multiple times, and builds a cumulative knowledge base across investigation sessions. The schema is straightforward:
CREATE TABLE state (
query TEXT,
query_type TEXT,
result TEXT,
source TEXT,
timestamp INTEGER
)
Recursive queries showcase the power of this architecture. When you query with --recurse=2, the tool doesn't just look up your initial domain—it extracts all IPs from those results, queries them, then extracts domains from those results and queries again. Here's the command-line usage:
# Find all infrastructure connected to suspicious domain within 2 hops
pdnstool -r 2 -s investigation.db -d DNSDB,CIRCL evil.example.com
# Export the relationship graph for visualization
pdnstool --state=investigation.db --graph=graphml > infrastructure.xml
The graph export functionality transforms your SQLite state database into GraphML, GDF, or Graphviz formats. Each DNS record becomes a node, and resolutions become edges. This turns passive DNS data into a visual network map showing how attackers' infrastructure interconnects—critical for understanding the scope of a campaign.
The configuration system uses a YAML file at ~/.passivedns-client to store API keys and provider preferences:
dnsdb:
apikey: your_farsight_key_here
server: https://api.dnsdb.info
circl:
username: analyst@company.com
password: circl_password
virusTotal:
apikey: vt_api_key
This keeps credentials out of scripts and command history. The library reads this config file at initialization and automatically configures each provider adapter. Rate limiting is handled per-provider using a token bucket algorithm—the library tracks request timestamps and sleeps when necessary to avoid hitting API limits.
One subtle design choice: the library performs all provider queries in parallel using threads. When you specify multiple providers with -d DNSDB,CIRCL,VirusTotal, it fires off three simultaneous requests rather than querying serially. This dramatically speeds up investigations but requires thread-safe result aggregation, which the library handles by collecting results into a synchronized array before normalization.
Gotcha
The biggest limitation is that this tool is only as good as your API access. Most passive DNS providers charge hundreds or thousands of dollars per month for API keys, and free tiers are severely rate-limited. DNSDB from Farsight, arguably the most comprehensive provider, starts at $1,500/month. If you're an independent researcher or small security team, you may only have access to one or two providers, which diminishes the value of a unified client. The tool also doesn't provide a mock mode or local data source for testing integrations without burning through paid API quotas.
The 240-second timeout is another red flag for production use. Recursive queries with high depth can easily exceed this limit, especially when combining multiple slow providers. There's no built-in retry logic for failed queries, so a single timeout in a recursive chain can leave gaps in your infrastructure map. The library also doesn't implement RFC 8427 (the Passive DNS Common Output Format standard), which means output won't be compatible with other standards-compliant tools without manual transformation. Finally, some providers (360.cn, OSC) appear to have stale implementations—checking the source code reveals minimal maintenance and potentially broken endpoints for lesser-used services.
Verdict
Use if: You're a security analyst or threat researcher who regularly investigates malicious infrastructure across multiple passive DNS sources, have budget for commercial API access, and need recursive pivoting with graph visualization. This tool shines for ad-hoc investigations where you're manually exploring attacker infrastructure rather than automated monitoring. It's also valuable if you want to avoid vendor lock-in by maintaining the ability to switch providers without rewriting tooling. Skip if: You only have access to one passive DNS provider (just use their native client), need real-time query performance under 10 seconds, require RFC-compliant output for integration with other security tools, or want to experiment with passive DNS on a budget (most providers require paid access). Also skip if you're building production security automation—the timeout behavior and lack of retry logic make it unsuitable for unattended operation.