The resolver kept its own query-log store (a bbolt ring, a live ring with SSE fanout, rolling per-domain counters and dashboard time-series) for the console. In a multi-node deployment each pod held a different slice of that, so every view disagreed with itself, and the cost sat on the query path. Charts belong in Grafana and log search in the log collector, so the store is gone. internal/accesslog writes one JSON line per notable query to stdout. The default is what was blocked and what failed (blocked, redirected, SERVFAIL, dropped, refused); log_cached and sample_rate add ordinary answers for a debugging session. Lines go through a bounded buffer to a single writer goroutine and are dropped, and counted in fancydns_access_log_dropped_total, when the stream cannot keep up, so a stalled log pipe can never stall a query. The config key is access_log (the old qlog block is ignored, like any unknown key). The metrics gain what the dashboard used to compute: fancydns_group_queries_total by ACL group and outcome and fancydns_responses_total by RCODE. Removed with the store: /api/dashboard, /api/logs, /api/logs/stream, /api/logs/clear, /api/cache/top, the series rings, and the cache's per-name hit table, which took a mutex and built a string on every cache hit. Tests that read the old log now read the access-log lines; the contract test drops the removed endpoints. The web console still calls them and is changed in the next commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
230 lines
6.5 KiB
YAML
230 lines
6.5 KiB
YAML
# fancy-dns — example configuration.
|
|
#
|
|
# Every value below is a default; delete what you do not need. The admin API
|
|
# rewrites this file when you save from the console, so keep it in git and
|
|
# review the diff.
|
|
|
|
version: 0
|
|
|
|
server:
|
|
# In-flight upstream resolutions. Excess queries get SERVFAIL rather than
|
|
# queueing without bound.
|
|
max_concurrent: 1024
|
|
timeouts:
|
|
udp: 2s
|
|
tcp: 4s
|
|
tls: 4s
|
|
query: 5s
|
|
upstream: 2s
|
|
list_fetch: 60s
|
|
|
|
dns:
|
|
udp: ["0.0.0.0:53", "[::]:53"]
|
|
tcp: ["0.0.0.0:53", "[::]:53"]
|
|
# Advertised EDNS0 buffer size for our own answers.
|
|
max_udp_size: 1232
|
|
serve_stale: 0s
|
|
edns0:
|
|
# Forward the client's EDNS-Client-Subnet so upstream picks a nearby
|
|
# anycast location.
|
|
client_subnet: true
|
|
ipv4_bits: 24
|
|
ipv6_bits: 56
|
|
preserve: true
|
|
dot:
|
|
enabled: false
|
|
listen: ["0.0.0.0:853"]
|
|
cert_file: /etc/fancy-dns/tls/fullchain.pem
|
|
key_file: /etc/fancy-dns/tls/privkey.pem
|
|
min_version: "1.2"
|
|
doh:
|
|
enabled: false
|
|
listen: ["0.0.0.0:443"]
|
|
base_path: /dns-query
|
|
cert_file: /etc/fancy-dns/tls/fullchain.pem
|
|
key_file: /etc/fancy-dns/tls/privkey.pem
|
|
min_version: "1.2"
|
|
allow_post: true
|
|
transport:
|
|
mode: auto
|
|
|
|
upstream:
|
|
# parallel: race them, take the first good answer. random: try N at random.
|
|
# failover: walk the list in order.
|
|
policy: parallel
|
|
max_attempts: 3
|
|
healthcheck_interval: 30s
|
|
breaker_threshold: 5
|
|
breaker_cooldown: 30s
|
|
servers:
|
|
- addr: 9.9.9.9:53
|
|
proto: udp
|
|
health_name: dns.quad9.net
|
|
- addr: 149.112.112.112:53
|
|
proto: udp
|
|
health_name: dns.quad9.net
|
|
- addr: 1.1.1.1:53
|
|
proto: tls
|
|
server_name: cloudflare-dns.com
|
|
- addr: 9.9.9.9:853
|
|
proto: tls
|
|
server_name: dns.quad9.net
|
|
|
|
# The memory envelope. A cgroup kill is an outage; a cache miss is not, so the
|
|
# runtime gets a soft ceiling to work towards instead of finding out the hard
|
|
# way. Set limit to your container's memory limit, minus the reserve.
|
|
memory:
|
|
# Soft heap ceiling (GOMEMLIMIT). 0 = unbounded. The Go heap excludes
|
|
# goroutine stacks, runtime metadata and the bbolt mmap for the cache
|
|
# snapshot and the query log, which is what the reserve is for.
|
|
limit: "0"
|
|
reserve: "256MiB"
|
|
# Byte budget for the in-memory LRU. 0 derives it from the limit (60%).
|
|
# cache.size alone does not bound memory: one cached ANY or TXT answer can
|
|
# be tens of kilobytes, so 500k entries is anywhere from 150MB to several GB
|
|
# depending on what the traffic asked for.
|
|
cache_max_bytes: "0"
|
|
|
|
cache:
|
|
enabled: true
|
|
size: 200000
|
|
shards: 32
|
|
min_ttl: 0s
|
|
max_ttl: 1h
|
|
# Keep expired answers around this long so a dead upstream costs latency,
|
|
# not availability.
|
|
serve_stale: 30m
|
|
prefetch: true
|
|
negative_ttl: 30m
|
|
persist:
|
|
enabled: true
|
|
path: data/cache.db
|
|
interval: 30s
|
|
max_entries: 500000
|
|
load_on_start: true
|
|
prewarm: []
|
|
|
|
blocklist:
|
|
enabled: true
|
|
# nxdomain | zero | redirect | drop | refused
|
|
default_action: nxdomain
|
|
response_ip: 0.0.0.0
|
|
response_ipv6: "::"
|
|
response_ttl: 60s
|
|
# RPZ-style policy zones and/or local authoritative data, as master files.
|
|
local_zones: []
|
|
# The one directory the admin console may WRITE zone data into: one
|
|
# <apex>.zone per zone, holding the SOA, the apex NS set and whatever the
|
|
# console created. Files in local_zones are read-only to the console, and
|
|
# are never rewritten by it. Empty keeps the console's Zones page
|
|
# read-only, so nothing is written behind your back.
|
|
managed_zones: ""
|
|
# Inline rules. "*.example.com" covers subdomains, "**.example.com" covers
|
|
# the apex too, a bare name matches only itself.
|
|
block_entries: []
|
|
allow_entries: []
|
|
lists:
|
|
- name: haas
|
|
format: plain
|
|
url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
|
path: data/lists/hosts
|
|
enabled: true
|
|
refresh: 24h
|
|
categories: [ads, tracking]
|
|
- name: easylist
|
|
format: adblock
|
|
url: https://easylist.to/easylist/easylist.txt
|
|
path: data/lists/easylist.txt
|
|
enabled: false
|
|
refresh: 24h
|
|
categories: [ads]
|
|
- name: local-rpz
|
|
format: rpz
|
|
path: zones/local.rpz.zone
|
|
enabled: true
|
|
refresh: 0s
|
|
|
|
acl:
|
|
# allow | deny | block_only
|
|
default_policy: allow
|
|
default_rate_limit: 0
|
|
groups:
|
|
- name: guests
|
|
comment: Blocklists apply, but the kitchen-sink allowlist does not.
|
|
match:
|
|
cidrs: ["192.168.90.0/24"]
|
|
policy: block_only
|
|
rate_limit: 50
|
|
- name: printers
|
|
comment: No recursion at all; they only need DHCP.
|
|
match:
|
|
cidrs: ["192.168.91.0/24"]
|
|
policy: deny
|
|
|
|
# One JSON line on stdout per query worth looking at, for whatever collects the
|
|
# pod's logs (Loki, a Grafana agent, `kubectl logs`). Nothing is stored here;
|
|
# the metrics count everything. The default is blocked queries and failures.
|
|
access_log:
|
|
enabled: true
|
|
log_blocked: true # blocklist hits and redirects
|
|
log_errors: true # SERVFAIL, drops, ACL refusals
|
|
log_cached: false # also cache hits
|
|
# Fraction of ordinary answers to log as well: 0 none, 1 all. For a short
|
|
# debugging session; leave at 0 otherwise.
|
|
sample_rate: 0
|
|
buffer: 4096 # lines that may wait for the log stream before dropping
|
|
|
|
cluster:
|
|
enabled: false
|
|
# node_id: dns-1
|
|
# advertise: http://10.0.0.10:8080
|
|
peer_ttl: 15s
|
|
valkey:
|
|
addrs: ["valkey:6379"]
|
|
prefix: fancydns
|
|
pool_size: 10
|
|
dial_timeout: 2s
|
|
read_timeout: 2s
|
|
# Only the lease holder fetches lists, so N instances do not hit a
|
|
# mirror N times.
|
|
leader_ttl: 30s
|
|
|
|
api:
|
|
enabled: true
|
|
listen: ["127.0.0.1:8080"]
|
|
# tls_listen: ["0.0.0.0:8443"]
|
|
base_path: /api
|
|
ui: true
|
|
allow_config_write: true
|
|
allow_blocklist_write: true
|
|
# Only these proxies may set X-Forwarded-For; anything else is ignored, so a
|
|
# client cannot spoof its identity past the ACL or the rate limiter.
|
|
trusted_proxies: []
|
|
cors_origins: []
|
|
auth:
|
|
# none | apikey | basic | oidc
|
|
mode: apikey
|
|
api_keys: ["CHANGE-ME-long-random-string"]
|
|
read_only_api_keys: []
|
|
trust_loopback: true
|
|
session_ttl: 12h
|
|
# For mode: oidc, configure a registered public PKCE client and the API
|
|
# audience included in its access tokens. Users need the admin role to write.
|
|
oidc_issuer: https://users.k6n.net
|
|
oidc_audience: https://fancy-dns.example.net
|
|
oidc_client_id: fancy-dns
|
|
oidc_write_roles: [admin]
|
|
rate_limit:
|
|
per_minute: 600
|
|
failures: 10
|
|
lockout: 5m
|
|
|
|
metrics:
|
|
enabled: true
|
|
path: /metrics
|
|
protect: true
|
|
|
|
log:
|
|
level: info
|
|
format: text
|