Skip to content

ASTRACAT2022/BalanceDNS

Repository files navigation

BalanceDNS (MVP+)

BalanceDNS is a lightweight DNS resolver/forwarder in Go with policy-based routing, sandboxed plugins (Lua and Go-exec), high-speed sharded LRU cache, and Prometheus metrics.

Features

  • DNS listeners: UDP/TCP + DoT + DoH (miekg/dns + net/http)
  • Processing chain: Blacklist -> Cache -> Lua/Plugin Policy -> Upstream
  • Multi-upstream routing by zone with automatic fallback between matching upstreams
  • Upstream protocols:
    • udp
    • tcp
    • dot (DNS-over-TLS)
    • doh (DNS-over-HTTPS)
  • Thread-safe sharded LRU cache (64 shards on large capacity) with min/max TTL bounds
  • Sandbox plugin engine:
    • Lua runtime in clean state without os, io, package loading
    • Go plugins executed as isolated subprocesses with strict timeout and empty environment
  • Listener/network stack tuning (reuse_port, reuse_addr, UDP size, read/write timeouts)
  • Built-in supervisor/control plane:
    • per-component restart with backoff
    • crash loop protection (max_consecutive_failure)
    • health/readiness/status endpoints
  • Prometheus metrics
  • Graceful shutdown (SIGINT, SIGTERM)

Metrics

  • balancedns_queries_total
  • balancedns_cache_hits_total
  • balancedns_upstream_latency_seconds
  • balancedns_plugin_execution_errors
  • balancedns_component_up
  • balancedns_component_restarts_total

Run

go run ./cmd/balancedns -config configs/prod.lua

Detailed Russian manual:

  • docs/MANUAL_RU.md
  • docs/POLICIES_RU.md (практика по политикам ответов)

Config

See configs/prod.lua.

Only Lua config is supported.

Lua config must return table:

return {
  listen = { dns = env("BALANCEDNS_DNS_ADDR", ":5353") },
  upstreams = {
    { name = "global-doh", protocol = "doh", doh_url = "https://dns.google/dns-query", zones = {"."} }
  }
}

Upstream example

upstreams = {
  {
    name = "global-doh",
    protocol = "doh",
    doh_url = "https://dns.google/dns-query",
    zones = {"."},
    timeout_ms = 1500
  },
  {
    name = "global-dot",
    protocol = "dot",
    addr = "1.1.1.1:853",
    tls_server_name = "cloudflare-dns.com",
    zones = {"."},
    timeout_ms = 1500
  }
}

Control plane

control = {
  restart_backoff_ms = 200,
  restart_max_backoff_ms = 5000,
  max_consecutive_failure = 0,
  min_stable_run_ms = 10000
}
  • 0 in max_consecutive_failure means unlimited restart attempts.
  • Health endpoints are served on metrics listener:
    • /healthz
    • /readyz
    • /statusz

Plugin contract

Lua plugin

function handle(question)
  -- question.domain, question.type, question.qtype
  return {
    action = "FORWARD" | "BLOCK" | "REWRITE" | "LOCAL_DATA",
    rewrite_domain = "example.org.",
    rewrite_type = "A",
    local_data = {
      ttl = 60,
      ip = "127.0.0.1",
      ips = {"127.0.0.1", "::1"}
    }
  }
end

Go-exec plugin

Plugin process receives JSON on stdin and returns JSON to stdout:

Input:

{"question":{"domain":"example.org.","type":"A","qtype":1}}

Output:

{"action":"FORWARD"}

or

{"action":"LOCAL_DATA","local_data":{"ips":["127.0.0.2"],"ttl":60}}

Plugin entries

plugins = {
  enabled = true,
  timeout_ms = 20,
  entries = {
    { name = "lua-policy", runtime = "lua", path = "./scripts/policy.lua" },
    { name = "go-policy", runtime = "go_exec", path = "/opt/balancedns/plugins/go-policy", timeout_ms = 10 }
  }
}

Example Go plugin source: scripts/go_policy_example.go (build it into executable and use runtime: "go_exec").

Tests and checks

go test ./...
go test -race ./...
go vet ./...
go build ./cmd/balancedns

Systemd install

Linux/systemd installer:

./scripts/install-systemd.sh

The script builds binary, installs config and creates balancedns.service with hardening options.

Docker

docker compose up -d --build

Files:

  • Dockerfile
  • docker-compose.yml
  • configs/prod.lua