Back to Articles

ioc2rpz: Turning Threat Intelligence Into DNS Policy Without Vendor Lock-In

[ View on GitHub ]

ioc2rpz: Turning Threat Intelligence Into DNS Policy Without Vendor Lock-In

Hook

91.3% of malware uses DNS for command-and-control. What if your DNS server could automatically block known threats before they reach your endpoints, updated in real-time from global threat intelligence feeds?

Context

Security teams drown in threat intelligence. Feeds arrive in countless formats—CSV files, JSON APIs, text lists—each describing malicious domains, IP addresses, and URLs that should never touch your network. Meanwhile, DNS remains the most universal control point in any infrastructure: every device, every application, every connection starts with a DNS query.

Response Policy Zones (RPZ) emerged as the IETF standard for DNS-based filtering, allowing administrators to override DNS responses based on policy rules. But RPZ adoption hit a wall: operationalizing threat intelligence into RPZ format required custom scripts, manual updates, and constant maintenance. Commercial solutions existed but locked you into vendor feeds and proprietary formats. ioc2rpz bridges this gap, acting as a universal translator that consumes threat intelligence in any format and serves it as standards-compliant RPZ feeds to any compatible DNS server. Built in Erlang/OTP—the same technology powering WhatsApp's messaging at global scale—it treats threat intelligence as a real-time data pipeline problem, not a security appliance.

Technical Insight

DNS Server Protocols

ioc2rpz OTP Application

Threat Intelligence Sources

IOC Lists

IOC Lists

IOC Lists

Manages

Raw Data

Extracted IOCs

RPZ Records

RPZ Records

RPZ Records

RPZ Records

Zone Transfers

Zone Metadata

HTTP/HTTPS/FTP Feeds

Local Files

Shell Scripts

Supervision Tree

Feed Fetcher Processes

Regex Parser

ETS Zone Cache

UDP:53 (SOA Queries)

TCP:53 (AXFR/IXFR)

DoT:853

DoH:443/8443 + REST API

Downstream DNS Servers

(BIND, PowerDNS)

System architecture — auto-generated

The architecture of ioc2rpz reveals why Erlang was the right choice for this problem domain. At its core, the system runs as a supervised OTP application where each threat feed becomes an independent process. When a feed update fails (connection timeout, malformed data, upstream outage), only that specific feed's process crashes and restarts—other feeds continue serving stale but valid data. This supervision tree pattern gives you fault isolation that would require significant complexity in languages without built-in process supervision.

Feed ingestion uses regex-based extraction with configurable patterns. Here's how you'd configure a feed pulling malicious domains from a text file:

%% In ioc2rpz.conf
{
  "sources": [{
    "name": "malware_domains",
    "url": "https://example.com/bad-domains.txt",
    "refresh": 3600,
    "regex": "^([a-z0-9.-]+\\.[a-z]{2,})$",
    "ioc_type": "domain",
    "action": "nxdomain"
  }]
}

When this feed updates, ioc2rpz fetches the content, applies the regex to each line, validates extracted IOCs, and stores them in ETS (Erlang Term Storage) tables. ETS gives you in-memory hash tables with concurrent read access—perfect for serving millions of DNS queries without locking. The zone data structure in memory looks conceptually like this:

%% Simplified representation of RPZ zone storage
-record(rpz_record, {
  fqdn,           %% Fully qualified domain name
  action,         %% nxdomain | passthru | drop | tcp-only
  source,         %% Which feed provided this IOC
  ttl,            %% Time to live
  expires_at      %% Unix timestamp for IOC expiration
}).

The expiration tracking is particularly clever. Unlike static blocklists that accumulate cruft, ioc2rpz lets you set per-feed expiration policies. An IOC from a "high-confidence" feed might live for 30 days, while something from a noisy feed expires in 24 hours. This temporal dimension to threat intelligence prevents your RPZ zones from becoming digital hoarder houses filled with decade-old indicators.

Zone serving happens over multiple protocols simultaneously. The same underlying ETS table powers UDP DNS queries (port 53), TCP AXFR zone transfers to downstream servers, DNS-over-TLS connections (port 853), and DNS-over-HTTPS via the Cowboy web server. Here's the critical architectural decision: ioc2rpz doesn't try to be a recursive DNS resolver. It's a pure authoritative server for RPZ zones. Your existing BIND or PowerDNS infrastructure does the heavy lifting of recursive resolution; it just consults ioc2rpz's RPZ feeds for policy decisions.

The REST API (also powered by Cowboy) gives you programmatic control over the system:

# Force an immediate feed update
curl -X POST http://localhost:8443/api/v1/update/malware_domains \
  -H "X-API-Key: your-secret-key"

# Check feed statistics
curl http://localhost:8443/api/v1/stats/malware_domains
{
  "total_iocs": 45231,
  "last_update": "2024-01-15T14:23:11Z",
  "active_iocs": 44890,
  "expired_iocs": 341,
  "feed_status": "healthy"
}

Feed mixing is where ioc2rpz shows its power. You can combine multiple threat feeds into a single RPZ zone with deduplication and priority handling. If domain "evil.com" appears in both a whitelist feed and a blocklist feed, you define which takes precedence. This turns your threat intelligence pipeline into a composable system where you can layer commercial feeds, open-source lists, and internal threat research into custom zones tailored to your risk tolerance.

The AXFR/IXFR implementation deserves attention. When a downstream BIND server requests a zone transfer, ioc2rpz serializes the entire ETS table into wire-format DNS records with proper SOA (Start of Authority) headers and incremental transfer support. For a zone with 5 million indicators, this happens in seconds because Erlang's binary handling and process-per-connection model excel at streaming large datasets. The TSIG authentication layer ensures only authorized servers can pull your threat intelligence zones, preventing adversaries from harvesting your complete IOC database.

Gotcha

The regex-based parsing is both a strength and a liability. While it provides flexibility to ingest virtually any feed format, you're responsible for writing and maintaining those patterns. When an upstream feed changes format—say, adding a new column to a CSV or switching from comma to pipe delimiters—your ioc2rpz instance silently stops extracting IOCs until you update the regex. There's no schema validation or automatic format detection. In production, this means you need monitoring on IOC extraction rates; a sudden drop to zero often indicates regex pattern rot, not an absence of threats.

Memory consumption scales linearly with indicator count. A zone with 10 million IOCs might consume 2-4GB of RAM depending on domain name lengths and metadata. While Erlang's garbage collection handles this gracefully, you can't escape the physics: ETS tables live entirely in memory. The documentation claims Raspberry Pi support, but that's realistic only for smaller deployments (under 1 million indicators). Enterprise-scale threat intelligence aggregation from multiple high-volume feeds demands substantial RAM. Unlike disk-backed databases, there's no overflow to disk—if you run out of memory, the VM crashes.

You also need to understand the deployment model clearly: ioc2rpz is not a drop-in DNS resolver for your clients. It doesn't answer queries for google.com or perform recursive lookups. It's a specialized authoritative server that your real DNS infrastructure (BIND, PowerDNS, Unbound with RPZ support) queries for policy decisions. This means you're running at least two DNS services: ioc2rpz for RPZ feeds and your existing resolver with RPZ enabled. The configuration complexity is higher than all-in-one solutions like Pi-hole, though you gain much more flexibility.

Verdict

Use if: You're operating DNS infrastructure at organizational scale with existing RPZ-capable servers (BIND, PowerDNS, Infoblox) and need to operationalize multiple threat intelligence feeds without vendor lock-in. It's ideal when you have diverse IOC sources (OSINT feeds, commercial subscriptions, internal research) that need standardization into a unified DNS policy layer. The Erlang foundation makes it perfect for high-availability deployments where feed failures shouldn't cascade into complete outages. Choose ioc2rpz when you value composability—mixing, filtering, and expiring indicators from heterogeneous sources according to your own risk policies.

Skip if: You lack RPZ-supporting DNS infrastructure and don't want to run separate authoritative and recursive DNS services. If you need a turnkey home or small office DNS filtering solution with a GUI, Pi-hole or AdGuard Home are dramatically simpler. Avoid it when your threat feeds exceed available RAM or when you need sub-second indicator propagation (the batch update model introduces minutes of latency). Also reconsider if you're uncomfortable with Erlang—while the configuration is JSON, any customization or troubleshooting requires understanding OTP supervision trees and ETS internals. Finally, if you want comprehensive threat intelligence management (correlation, enrichment, analyst workflows), look at full platforms like MISP or OpenCTI that include DNS integration as one component rather than the entire focus.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/homas-ioc2rpz.svg)](https://starlog.is/api/badge-click/developer-tools/homas-ioc2rpz)