Documentation

Comprehensive guides to help you install, configure, and get the most out of EtchDNS.

Installation

From Release Binaries

Download the latest release from the releases page.

From Source

  1. Ensure you have Rust and Cargo installed
  2. Clone the repository
  3. Build the release version:
cargo build --release

The executable will be available at target/release/etchdns.

Building with WebAssembly Hooks Support

By default, EtchDNS is built without WebAssembly hooks support to keep the binary size smaller. If you want to enable the hooks functionality, build with the hooks feature flag:

cargo build --release --features hooks

Note that enabling the hooks feature includes a WebAssembly runtime which significantly increases the binary size. Only enable this feature if you plan to use WebAssembly extensions.

Configuration

EtchDNS uses a TOML configuration file to control all aspects of its behavior. A complete example with documentation can be found in the included etchdns.toml file.

Basic Server Settings

# Basic server configuration
listen_addresses = ["127.0.0.1:53", "[::1]:53"]
log_level = "warn"
max_udp_packet_size = 1232
max_tcp_packet_size = 65535

Upstream DNS Servers

# Upstream DNS servers
upstream_servers = [
  "8.8.8.8:53",
  "1.1.1.1:53",
  "9.9.9.9:53"
]
server_timeout = 2.0  # seconds

Running EtchDNS

To run EtchDNS with your configuration file:

etchdns -c /path/to/etchdns.toml

Domain Filtering

Allowed Zones

Create a text file with domains that should be allowed:

# Company domains
example.com
example.org

# Third-party services
github.com
google.com

Then reference this file in your configuration:

allowed_zones_file = "allowed_zones.txt"

NX Zones (Blocklist)

Create a text file with domains that should return NXDOMAIN:

# Advertising domains
ads.example.com
analytics.example.com

# Known malicious domains
malware.example.net

Then reference this file in your configuration:

nx_zones_file = "nx_zones.txt"

Load Balancing

EtchDNS supports multiple load balancing strategies to distribute queries across upstream DNS servers:

# Load balancing configuration
load_balancing_strategy = "fastest"  # Options: "random", "fastest", "p2"
probe_interval = 60.0  # seconds

Available Strategies

  • random: Randomly selects an upstream server for each query
  • fastest: Selects the server with the lowest response time based on periodic probes
  • p2: Power-of-two-choices strategy that selects the faster of two randomly chosen servers

Caching

EtchDNS implements an efficient DNS caching system using the SIEVE algorithm which provides optimal memory usage and fast lookups:

# Cache configuration
cache_size = 10000                # Number of entries
cache_ttl_cap = 86400            # Maximum 1 day cache time, even if TTL is longer
cache_in_memory_only = false     # Set to true to disable mmap-based caching
negative_cache_ttl = 60           # Seconds for negative responses
min_cache_ttl = 1                 # Minimum seconds to cache responses
ttl_multiply_factor = 1.0         # Optionally adjust all TTLs (e.g. 0.5 = half)
serve_stale_grace_time = 86400    # Seconds (1 day) to serve stale entries during failures
serve_stale_ttl = 30              # TTL for stale entries when served
prefetch_popular_queries = false  # Set to true to preemptively refresh popular entries
prefetch_threshold = 10           # Prefetch entries seen at least this many times

Cache Size and Storage

  • cache_size: The maximum number of entries to store in the cache. Choose based on available memory and expected query volume. Recommended values:
    • Small networks: 5,000 - 10,000 entries
    • Medium networks: 20,000 - 50,000 entries
    • Large deployments: 100,000+ entries
  • cache_in_memory_only: By default, EtchDNS can use memory-mapped files for cache storage to handle large caches efficiently. Set to true for pure in-memory caching.

TTL Management

EtchDNS provides flexible TTL (Time To Live) management to balance freshness and performance:

  • min_cache_ttl: Minimum time in seconds that a valid response will be stored in the cache. This ensures that responses with very short TTLs are still cached for a reasonable amount of time. The minimum TTL is applied when caching responses in both authoritative and non-authoritative modes. In authoritative mode, the original TTL is still returned to clients, while in non-authoritative mode, the TTL is adjusted based on the remaining cache time.
  • cache_ttl_cap: Maximum TTL applied to cache entries, even if the authoritative TTL is longer. This prevents overly long cache times.
  • ttl_multiply_factor: Multiply all TTLs by this factor. Use values below 1.0 to shorten cache times for fresher data, or above 1.0 to extend cache times for better performance.
  • negative_cache_ttl: TTL in seconds to use for negative DNS responses (like NXDOMAIN results) when no TTL is available in the response.

Serve Stale

The "serve stale" feature allows EtchDNS to return expired DNS cache entries when upstream resolution fails or times out. This improves reliability during upstream server outages.

  • serve_stale_grace_time: How long after expiration a cache entry can still be served if upstream resolution fails. Default is 86400 (1 day).
  • serve_stale_ttl: The TTL value to use for stale entries that are served. This TTL is reported to clients when providing stale data.

Cache Prefetching

EtchDNS can proactively refresh popular cache entries before they expire, ensuring seamless responses for frequently accessed domains:

  • prefetch_popular_queries: When enabled, EtchDNS will automatically refresh frequently accessed cache entries before they expire.
  • prefetch_threshold: The number of times an entry must be accessed to be considered "popular" and qualify for prefetching.

Cache Management API

EtchDNS provides an HTTP API for cache management via the control interface:

  • GET /control/cache: View cache statistics
  • DELETE /control/cache: Clear the entire cache
  • DELETE /control/cache/zone/<example.com>: Clear all entries for a specific zone
  • DELETE /control/cache/name/<www.example.com>: Clear a specific cached name

Security

IP Validation

# IP validation configuration
enable_strict_ip_validation = false  # Enable advanced IP validation features
block_private_ips = false            # Block 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
block_loopback_ips = false           # Block 127.0.0.0/8, ::1
min_client_port = 1024               # Block connections from privileged ports
blocked_ip_ranges = [                # Custom CIDR blocks to reject
  # "203.0.113.0/24"                 # Example: Block TEST-NET-3 range
]

IP validation provides protection against spoofed addresses and requests from potentially malicious sources. When enabled, each client connection is validated before processing DNS requests. The system includes:

  • Port validation: Blocks connections from privileged ports (below 1024) which could indicate spoofing
  • Origin validation: Drops queries coming from your own server's IPs to prevent DNS amplification attacks
  • Custom range blocking: Silently rejects connections from specified IP ranges
  • Private/loopback blocking: Optional filtering of private network ranges and loopback addresses

Connections from blocked IPs are silently dropped without any response, increasing security by not revealing the server's existence to potentially malicious clients. The recently improved implementation provides more efficient connection filtering with minimal impact on performance.

Rate Limiting

# Rate limiting configuration
max_udp_clients = 1000
max_tcp_clients = 100
max_doh_connections = 100
rate_limit_udp = 100  # queries per second per client
rate_limit_tcp = 20   # queries per second per client
rate_limit_doh = 10   # queries per second per client
rate_limit_table_size = 50000

Privilege Dropping

# Privilege dropping
user = "nobody"
group = "nogroup"
chroot = "/var/empty"

Monitoring

Query Logging

EtchDNS can log DNS queries to a file for monitoring, troubleshooting, and security analysis. Query logging is configurable and supports automatic log rotation.

# Query logging configuration
query_log_file = "queries.log"  # Path to log file (empty to disable logging)
query_log_include_timestamp = true  # Include timestamp in log entries
query_log_include_client_addr = true  # Include client IP address
query_log_include_query_type = true  # Include DNS query type (e.g., A, AAAA, MX)
query_log_include_query_class = false  # Include DNS query class

Log Rotation

EtchDNS supports automatic log rotation to manage log file sizes and implement log retention policies. Log files can be rotated based on size, time intervals, or both. Rotated log files can optionally be compressed.

# Log rotation configuration
query_log_rotation_size = 10485760  # Rotate when log reaches 10MB (0 to disable)
query_log_rotation_interval = "daily"  # Options: hourly, daily, weekly, monthly, never
query_log_rotation_count = 7  # Keep 7 rotated log files (older ones are deleted)
query_log_compression = true  # Compress rotated logs with gzip

The log rotation system works as follows:

  • Size-based rotation: When a log file exceeds the specified size in bytes
  • Time-based rotation: At the specified interval (hourly, daily, weekly, or monthly)
  • File naming: Rotated log files are named original_name.timestamp
  • Compression: When enabled, rotated files are compressed with gzip to save space
  • Retention: Only the specified number of most recent log files are kept

Note: For production environments, it's recommended to enable log rotation to prevent logs from consuming too much disk space. A common configuration is daily rotation with 7-30 days of retention, depending on your compliance and troubleshooting needs.

Remote Control API

# Control API setup
control_listen_addresses = ["127.0.0.1:8080"]
control_path = "/control"

Available endpoints:

  • GET /control/status: Server status
  • GET /control/cache: Get cache status information
  • DELETE /control/cache: Clear entire cache
  • DELETE /control/cache/zone/<example.com>: Clear entries for a specific zone
  • DELETE /control/cache/name/<example.com>: Clear a specific entry

WebAssembly Extensions

EtchDNS can be extended through WebAssembly modules, allowing you to implement custom DNS processing logic in any language that compiles to WebAssembly.

# WebAssembly hooks configuration
hooks_wasm_file = "hooks.wasm"
hooks_wasm_wasi = false  # Set to true if your plugin needs WASI support

Understanding WebAssembly Extensions

WebAssembly (Wasm) allows you to extend EtchDNS's functionality without modifying the core code. You can write custom logic to filter queries, modify responses, implement custom logging, or any other processing you need.

Available Extension Points

EtchDNS currently supports the following hook points:

  • hook_client_query_received: Called when a client DNS query is received, before checking the cache
  • hook_response_prepared: Called before a response is sent back to the client
  • hook_cache_lookup_response: Called when a cached response is about to be returned

Building Extensions with Zig

Zig is a modern, high-level programming language that compiles efficiently to WebAssembly and is well-suited for creating EtchDNS extensions. EtchDNS includes a complete Zig-based example plugin in the webassembly-plugins/zig/ directory.

To build a custom extension with Zig:

  1. Install Zig from https://ziglang.org/download/
  2. Examine the provided example in webassembly-plugins/zig/src/main.zig
  3. Build the WebAssembly module:
cd webassembly-plugins/zig
zig build
# The output will be in zig-out/lib/hooks.wasm

Zig Example Implementation

The Zig example illustrates how to implement a simple plugin that filters domains based on certain criteria:

// Sample from webassembly-plugins/zig/src/main.zig
export fn hook_client_query_received(
    qname_ptr: [*]const u8,
    qname_len: usize,
    qtype: u16,
    qclass: u16,
) i32 {
    // Access DNS query fields
    const qname = qname_ptr[0..qname_len];
    
    // Example filtering logic
    if (std.mem.indexOf(u8, qname, "ads") != null) {
        // Block queries containing "ads"
        return -1;
    }
    
    // Allow all other queries
    return 0;
}

WebAssembly API Reference

EtchDNS exports the following interfaces to WebAssembly modules:

Host Functions Available to Wasm Modules

  • log_debug(message_ptr: *const u8, message_len: usize): Log a debug message
  • log_info(message_ptr: *const u8, message_len: usize): Log an info message
  • log_warn(message_ptr: *const u8, message_len: usize): Log a warning message
  • log_error(message_ptr: *const u8, message_len: usize): Log an error message
  • get_configuration_value(key_ptr: *const u8, key_len: usize, value_ptr: *u8, value_max_len: usize): Get a configuration value
  • set_response_ttl(ttl: u32): Modify the TTL of a DNS response

Wasm Module Exports Required by EtchDNS

  • hook_client_query_received(qname_ptr: *const u8, qname_len: usize, qtype: u16, qclass: u16) -> i32: Return 0 to allow the query, -1 to block it, or a positive value for a custom response code.

Using WebAssembly Extensions

To use a WebAssembly extension:

  1. Build EtchDNS with WebAssembly support: cargo build --release --features hooks
  2. Place your compiled .wasm file in an accessible location
  3. Add the configuration to your etchdns.toml:
# WebAssembly hooks configuration
hooks_wasm_file = "/path/to/your/hooks.wasm"
hooks_wasm_wasi = false  # Set to true if your plugin needs WASI support

For more details on building WebAssembly extensions, see the WebAssembly Extension Guide and the examples in the webassembly-plugins/ directory.

Performance Tuning

For optimal performance, consider these configuration guidelines:

  1. Cache Size: Increase cache_size based on available memory
  2. Client Limits: Adjust max_udp_clients and max_tcp_clients for your environment
  3. Load Balancing: Use fastest for highest performance, p2 for a good balance
  4. Serve Stale: Enable serve_stale_grace_time for improved reliability
  5. Rate Limiting: Set appropriate limits that prevent DoS while allowing legitimate traffic

Quick Start Tutorials

The following tutorials provide step-by-step instructions for common EtchDNS deployment scenarios.

Setting Up as a Secondary DNS Server

This tutorial demonstrates how to set up EtchDNS as a secondary authoritative DNS server.

  1. Install EtchDNS following the installation instructions.
  2. Create a configuration file named etchdns.toml with the following content:
# Secondary DNS server configuration
listen_addresses = ["0.0.0.0:53"]  # Listen on all interfaces, port 53
log_level = "info"                 # Adjust as needed

# Authoritative mode enabled
authoritative_dns = true

# Upstream servers (your primary DNS servers)
upstream_servers = [
  "primary-dns-1.example.com:53",
  "primary-dns-2.example.com:53"
]

# Load balancing - fastest is recommended for secondary DNS
load_balancing_strategy = "fastest"
probe_interval = 30.0  # Check primary servers every 30 seconds

# Cache configuration 
cache_size = 10000
min_cache_ttl = 60     # Minimum 60 seconds cache time
serve_stale_grace_time = 86400  # 1 day
serve_stale_ttl = 30

# Security settings
max_udp_clients = 1000
max_tcp_clients = 100
rate_limit_udp = 100   # Queries per second per client
rate_limit_tcp = 20    # Queries per second per client
                
  1. Launch EtchDNS with your configuration file:
sudo etchdns -c /path/to/etchdns.toml

Note: Running on port 53 requires root privileges. For production, consider using privilege dropping:

# Add to your configuration file
user = "nobody"
group = "nogroup"
                
  1. Update your DNS infrastructure to point to your EtchDNS server as a secondary.
  2. Verify the setup by querying your EtchDNS server:
dig @your-etchdns-server example.com

Setting Up as a Local DNS Cache

This tutorial demonstrates how to set up EtchDNS as a local DNS cache for your network.

  1. Install EtchDNS following the installation instructions.
  2. Create a configuration file named etchdns.toml with the following content:
# Local DNS cache configuration
listen_addresses = ["192.168.1.100:53"]  # Replace with your server's IP
log_level = "info"                       # Adjust as needed

# Caching mode (default)
authoritative_dns = false

# Public DNS resolvers (choose reliable providers)
upstream_servers = [
  "8.8.8.8:53",   # Google DNS
  "1.1.1.1:53",   # Cloudflare DNS
  "9.9.9.9:53"    # Quad9 DNS
]

# Load balancing strategy
load_balancing_strategy = "p2"  # Good balance of performance and reliability
probe_interval = 60.0           # Check servers every minute

# Caching configuration for optimal performance
cache_size = 50000              # Adjust based on available RAM
min_cache_ttl = 30              # Minimum 30 seconds cache time
negative_cache_ttl = 60         # 1 minute for negative responses
serve_stale_grace_time = 86400  # Serve stale entries for up to 1 day during outages
serve_stale_ttl = 30            # TTL for stale entries

# Security settings
max_udp_clients = 1000
rate_limit_udp = 100            # Queries per second per client
                
  1. Launch EtchDNS with your configuration file:
sudo etchdns -c /path/to/etchdns.toml
  1. Configure your network to use EtchDNS as the primary DNS server. This can be done via:
  • Router DHCP settings
  • Manual device configuration
  • DHCP server configuration
  1. Verify the setup by testing DNS resolution:
dig @192.168.1.100 example.com

Note: For better security in a local network, consider enabling domain filtering:

# Add to your configuration file
nx_zones_file = "nx_zones.txt"  # Blocklist for malicious domains
                

Deploying with WebAssembly Extensions

This tutorial demonstrates how to set up EtchDNS with a custom WebAssembly extension.

  1. Build EtchDNS with WebAssembly support:
cargo build --release --features hooks
  1. Create or obtain a WebAssembly module - EtchDNS includes examples in webassembly-plugins/zig/
  2. Build the Zig example (if using the provided example):
cd webassembly-plugins/zig
zig build
cp zig-out/lib/hooks.wasm ../../hooks.wasm
  1. Update your configuration file to use the WebAssembly module:
# WebAssembly hooks configuration
hooks_wasm_file = "hooks.wasm"
hooks_wasm_wasi = false  # Set to true if your plugin needs WASI
  1. Launch EtchDNS with your configuration file:
./target/release/etchdns -c /path/to/etchdns.toml

Tip: WebAssembly extensions give you incredible flexibility. You can use them to implement custom filtering rules, modify DNS responses, add custom logging, or implement business logic specific to your needs.

Configuration Examples

These ready-to-use configuration examples address common use cases and can be adapted to your needs.

High-Performance DNS Proxy

# High-performance DNS proxy configuration
listen_addresses = ["0.0.0.0:53"]
log_level = "warn"  # Minimal logging for performance

# Upstream DNS (choose servers with good latency)
upstream_servers = [
  "8.8.8.8:53",
  "1.1.1.1:53",
  "9.9.9.9:53"
]

# Fastest server selection for optimal performance
load_balancing_strategy = "fastest"
probe_interval = 30.0

# Aggressive caching
cache_size = 100000              # Large cache
min_cache_ttl = 30               # Min 30s cache even for short TTLs
serve_stale_grace_time = 86400   # Keep expired entries for 1 day
serve_stale_ttl = 30             # Use 30s TTL for stale entries
prefetch_popular_queries = true  # Keep popular entries fresh

# High connection limits for busy servers
max_udp_clients = 10000
max_tcp_clients = 1000
max_concurrent_queries = 10000

# Monitor with metrics
metrics_listen_address = "127.0.0.1:9100"

Secure DNS Firewall

# DNS firewall configuration
listen_addresses = ["0.0.0.0:53", "[::]:53"]
log_level = "info"

# Upstream servers
upstream_servers = [
  "1.1.1.2:53",  # Cloudflare Malware Blocking DNS
  "9.9.9.9:53"   # Quad9 Secure DNS
]

# Domain filtering
nx_zones_file = "nx_zones.txt"  # Blocklist
allowed_zones_file = "allowed_zones.txt"  # Optional whitelist

# IP validation
enable_strict_ip_validation = true
block_private_ips = true
block_loopback_ips = true
min_client_port = 1024
blocked_ip_ranges = [
  "203.0.113.0/24",  # Example: Block TEST-NET-3 range
  "198.51.100.0/24"  # Example: Block TEST-NET-2 range
]

# Strict rate limiting
rate_limit_udp = 50   # Queries per second per client
rate_limit_tcp = 10   # Queries per second per client

# Query logging for security analysis
query_log_file = "/var/log/etchdns/queries.log"
query_log_include_timestamp = true
query_log_include_client_addr = true
query_log_include_query_type = true

# Log rotation for security logs
query_log_rotation_size = 52428800  # 50MB
query_log_rotation_interval = "daily"
query_log_rotation_count = 30  # Keep 30 days of logs
query_log_compression = true   # Save space with compression

# Privilege dropping for security
user = "nobody"
group = "nogroup"
chroot = "/var/empty"

DoH Server with WebAssembly Extensions

# DoH server with WebAssembly extensions
listen_addresses = ["127.0.0.1:53"]  # Standard DNS port
log_level = "info"

# DoH endpoint
doh_listen_addresses = ["0.0.0.0:443"]  # Standard HTTPS port
doh_certificate_file = "/path/to/cert.pem"
doh_key_file = "/path/to/key.pem"
doh_path = "/dns-query"  # Standard DoH path

# Upstream servers
upstream_servers = [
  "8.8.8.8:53",
  "1.1.1.1:53"
]

# Cache configuration
cache_size = 50000
min_cache_ttl = 30
serve_stale_grace_time = 86400

# WebAssembly hooks
hooks_wasm_file = "hooks.wasm"
hooks_wasm_wasi = false

# Rate limits for DoH
max_doh_connections = 1000
rate_limit_doh = 20  # Queries per second per client

# Remote control API
control_listen_addresses = ["127.0.0.1:8080"]
control_path = "/control"