# lazydns v0.3 — Release Notes
A high-performance DNS forwarder with a built-in web dashboard, written in Rust.
This release covers v0.3.0 through v0.2. It adds a real-time WebUI, cache persistence, DNSSEC-safe caching, and simplifies the configuration experience.
---
## Highlights
### Web dashboard
lazydns now ships with a built-in web dashboard (Svelte + axum). No external tools needed to see what your DNS server is doing.
- **Live query logs**: every DNS query streams in real time via SSE, with client IP, domain, response time, and answers
- **Security events**: rate-limit violations, blocked domains, upstream failures, and ACL denials appear instantly
- **Upstream health**: per-upstream success rate, average latency, and failure counts
- **Alert engine**: configurable rules with deduplication and webhook notifications
- **Dark mode**, responsive layout, embedded assets (single binary with `web-embed`)
Enable it in your config:
```yaml
web:
enabled: true
listen: "0.0.0.0:8002"
```
Build with `--features web-embed` to compile the dashboard into the binary.
### Configuration viewer
The Admin page now has a **Configuration** tab that shows your currently loaded plugins, sequences (as a visual step-by-step flow), and server settings. No more guessing what your server is actually running. Click any plugin to expand its full arguments.
When a config reload fails, the error message from the validator is shown inline so you can fix the problem without digging through logs.
### Cache persistence
The cache can now survive server restarts. Set `dump_file` and lazydns saves cached responses to a binary file on shutdown, then restores them on startup (skipping any that have expired).
```yaml
- tag: cache
type: cache
args:
size: 2048
enable_lazycache: true
dump_file: /var/lib/lazydns/cache.dump
dump_interval: 300
```
### Simpler configuration
We removed confusing and dead config options across the board:
- **`fallback`**: removed `threshold` and `always_standby` keys that were silently ignored (never implemented).
- **`domain_validator`**: removed the `blacklist` option. Use `domain_set` + `black_hole` for domain blocking, which is more capable and supports file-based lists.
- **`cache`**: removed 5 internal tuning knobs (`refresh_worker_count`, `refresh_queue_capacity`, `enable_cleanup`, `cleanup_interval_secs`, `cleanup_pressure_threshold`). These are now sensible built-in defaults.
- **`priority`**: removed entirely. The sequence order in your config determines execution order; the `priority` field never did anything.
---
## Bug fixes
### Forwarding
- UDP responses were cross-pollinated between concurrent queries on the shared socket. Now multiplexed by query ID with a dedicated demux loop.
- A response arriving at the exact moment of a timeout could be dropped. Now uses a fair select so the response wins.
- Average response time (used by the `fastest` load-balancing strategy) was updated non-atomically, losing samples under concurrency. Fixed with CAS.
- Replaced `std::sync::Mutex` with `parking_lot::Mutex` to avoid lock poisoning on panics.
### Cache
- Cached responses were mutated in place when served, corrupting the cache for subsequent queries. Now deep-cloned.
- The question section of cached responses was not synced to the current request, causing query/response mismatches on cache hits.
- A cache key stayed marked "refreshing" forever after the first LazyCache background refresh, silently disabling prefetch for that key. Now cleared via a completion hook.
- Cache keys did not include DNSSEC flags (DO/AD/CD), causing DNSSEC-enabled responses to be served to clients that did not request DNSSEC. Now included.
### Servers
- TCP/DoT responses larger than 65535 bytes silently truncated the length prefix, corrupting the stream. Now clamped with an error.
- DoQ `local_addr()` would panic on failure. Now degrades gracefully.
### Config
- The `config-simple.yaml` example used an unimplemented sequence syntax that produced a no-op sequence. Fixed.
- Upstream address formats were inconsistent across examples. Standardized to `addr:` mapping form.
---
## Protocol support
All five DNS transport protocols are supported:
| Protocol | Config type | Port example |
|---|---|---|
| UDP | `udp_server` | `:53` |
| TCP | `tcp_server` | `:53` |
| DoT | `dot_server` | `:853` |
| DoH | `doh_server` | `:443` |
| DoQ | `doq_server` | `:853` |
DoT, DoH, and DoQ require TLS certificates (`cert_file` / `key_file`).
---
## Feature flags
```bash
# Minimal (UDP/TCP only, no web)
cargo build
# With WebUI dashboard
cargo build --features web
# With embedded WebUI (single binary)
cargo build --features web-embed
# Everything
cargo build --features full
```
---
## Getting started
```bash
# Build
cargo build --features web-embed
# Run with the example config
./target/debug/lazydns -c examples/etc/config.yaml -d examples/etc/
# Open the dashboard
open http://127.0.0.1:8002
```
Full documentation: https://lazywalker.github.io/lazydns/docs/