lazydns 0.3.20

A light and fast DNS server/forwarder implementation in Rust
Documentation
# 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/