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.
- 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:
udptcpdot(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
- Lua runtime in clean state without
- 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)
balancedns_queries_totalbalancedns_cache_hits_totalbalancedns_upstream_latency_secondsbalancedns_plugin_execution_errorsbalancedns_component_upbalancedns_component_restarts_total
go run ./cmd/balancedns -config configs/prod.luaDetailed Russian manual:
docs/MANUAL_RU.mddocs/POLICIES_RU.md(практика по политикам ответов)
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 = {"."} }
}
}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 = {
restart_backoff_ms = 200,
restart_max_backoff_ms = 5000,
max_consecutive_failure = 0,
min_stable_run_ms = 10000
}0inmax_consecutive_failuremeans unlimited restart attempts.- Health endpoints are served on metrics listener:
/healthz/readyz/statusz
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"}
}
}
endPlugin 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}}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").
go test ./...
go test -race ./...
go vet ./...
go build ./cmd/balancednsLinux/systemd installer:
./scripts/install-systemd.shThe script builds binary, installs config and creates balancedns.service with hardening options.
docker compose up -d --buildFiles:
Dockerfiledocker-compose.ymlconfigs/prod.lua