狗狗 DNS,可以 DaemonSet 或中心化权威部署?暂时不完善,但作为本地代理完全冇问题。想试试 AI Coding 有多强,一天做了在以前应该需要一两个月的事,主要是作者水平不够而它拔高了所有技术能力。几乎所有代码由 AI 生成,只小改了些字符!!!
A Rust-based service discovery DNS server built on hickory-dns, inspired by CoreDNS's plugin architecture. Resolves service names from registries like Nacos to IP addresses via standard DNS queries.
DNS Query (UDP/TCP/TLS/HTTPS)
|
v
+--------------------+
| DoggyDnsHandler |
+--------------------+
|
+-----------------+-----------------+
| |
v v
Middleware Chain Authority Chain
(before / after) (sequential lookup)
| |
+---------+---------+ +-------------+-------------+
| | | | |
v v v v v
Logging Metrics Nacos Native Forward
Middleware Middleware Authority Authority Authority
doggy-dns/ # Binary entrypoint
crates/
doggy-dns-plugin/ # Plugin traits, authority chain, built-in authorities
doggy-dns-core/ # Server runtime, config, handler, middleware
doggy-dns-nacos/ # Nacos service discovery backend
Dependency graph:
doggy-dns(binary) ->doggy-dns-core,doggy-dns-plugin,doggy-dns-nacosdoggy-dns-core->doggy-dns-plugindoggy-dns-nacos->doggy-dns-plugindoggy-dns-pluginis the leaf crate (no internal deps)
- Middleware chain runs
before_request()on each middleware in order. If any returnsShortCircuit, the request is refused immediately. - Authority chain iterates handlers sequentially. Each handler returns one of:
Skip-- does not handle this query, try the next handlerContinue(Ok(records))-- resolved, return recordsContinue(Err(e))/Break(Err(e))-- error, return SERVFAIL
- If all handlers skip, the response is
NXDOMAIN. - Middleware chain runs
after_request()with elapsed time.
Two plugin axes:
| Layer | Trait | Purpose | Implementations |
|---|---|---|---|
| Middleware | Middleware |
Cross-cutting concerns | LoggingMiddleware, MetricsMiddleware |
| Authority | ZoneHandler |
DNS data sources | NacosAuthority, NativeAuthority, ForwardAuthority |
Authority plugins:
| Kind | Description |
|---|---|
nacos |
Resolves service names from Nacos registry via DashMap cache. Format: <service>.<group>.<namespace>.<zone>. |
native |
Resolves using the OS system resolver (/etc/resolv.conf) |
forward |
Resolves using explicit upstream DNS servers (e.g., 8.8.8.8:53) |
Plugins are evaluated in the order defined in the [[plugins]] config array. The first handler that returns a non-Skip result wins.
- Connects to Nacos naming service via
nacos-sdk - Subscribes to all services on startup; receives push updates on instance changes
- Maintains an in-memory
DashMap<ServiceKey, Vec<CachedInstance>>cache - DNS name format:
<service>.<group>.<namespace>.<zone_suffix>. - Example:
dig user-service.DEFAULT_GROUP.public.nacos.local A - Only handles
AandAAAArecord types; all other types are skipped
| Protocol | Default Port | Description |
|---|---|---|
| UDP | config port |
Standard DNS |
| TCP | config port |
Standard DNS over TCP |
| TLS (DoT) | 853 | DNS over TLS, requires [server.tls] |
| HTTPS (DoH) | 443 | DNS over HTTPS at /dns-query, requires [server.https] |
Structured logging via tracing. Key log events:
| Event | Level | Fields |
|---|---|---|
| Query resolved | INFO |
query, qtype, authority, answers, duration_ms, response_code |
| No authority handled query | INFO |
query, qtype, response_code=NXDomain |
| Authority returned error | WARN |
query, qtype, authority, error, response_code=ServFail |
| Middleware short-circuit | WARN |
duration_ms, response_code=Refused |
| Configuration loaded | INFO |
listen_addr, port, worker_threads, plugin_count, ... |
Control log level via the RUST_LOG environment variable.
TOML-based. Default path: config/doggy-dns.toml (or pass as CLI arg).
[server]
listen_addr = "0.0.0.0"
port = 15353
tcp_timeout = 10 # seconds, default 10
shutdown_timeout = 5 # seconds, default 5
worker_threads = 1 # default 1 (suitable for DaemonSet, one per node)
# Optional: DNS over TLS
#[server.tls]
#enabled = true
#port = 853
#cert_path = "/path/to/cert.pem"
#key_path = "/path/to/key.pem"
# Optional: DNS over HTTPS
#[server.https]
#enabled = true
#port = 443
#cert_path = "/path/to/cert.pem"
#key_path = "/path/to/key.pem"
[middleware]
logging = true
metrics = false
# Plugins are evaluated in order. First non-Skip result wins.
[[plugins]]
kind = "nacos"
enabled = false
server_addr = "127.0.0.1:8848"
namespace = "public"
group = "DEFAULT_GROUP"
dns_zone = "nacos.local"
ttl = 6
[[plugins]]
kind = "native"
enabled = true
cache_size = 4096 # default 4096
min_ttl = 10 # seconds, default 10
max_ttl = 300 # seconds, default 300
[[plugins]]
kind = "forward"
enabled = true
upstream = ["8.8.8.8:53", "1.1.1.1:53"]
cache_size = 1024
min_ttl = 30
max_ttl = 600
# Optional: hot-reload config from Nacos config service
[remote_config]
enabled = false
server_addr = "127.0.0.1:8848"
namespace = "public"
group = "DOGGY_DNS"
data_id = "doggy-dns.toml"If no [[plugins]] are configured, defaults to forwarding to 8.8.8.8 and 1.1.1.1.
cargo build --release# With default config
./target/release/doggy-dns config/doggy-dns.toml
# With debug logging
RUST_LOG=doggy_dns=debug ./target/release/doggy-dns config/doggy-dns.toml# Run all tests
cargo test --workspace
# Query the server (once running)
dig @127.0.0.1 -p 15353 www.google.com A
dig @127.0.0.1 -p 15353 user-service.DEFAULT_GROUP.public.nacos.local ABenchmarks use criterion and parameterize over worker_threads 1..4.
# Run all benchmarks (nacos, authority chain, handler pipeline)
cargo bench --workspace
# Run a single crate's benchmarks
cargo bench -p doggy-dns-core # handler pipeline benchmarks
cargo bench -p doggy-dns-nacos # nacos authority lookup benchmarks
cargo bench -p doggy-dns-plugin # authority chain benchmarks
# Run a single benchmark by name
cargo bench --bench handler_bench
cargo bench --bench nacos_bench
cargo bench --bench chain_bench
# Filter to a specific benchmark function
cargo bench --bench handler_bench -- handler_pipeline_nacos_hit
# Compile-only check (used in CI)
cargo bench --no-run --workspaceNote:
cargo benchoutput starts with "running 0 tests" lines from the default test harness for each crate. This is normal -- the actual criterion results follow immediately after.
Uses cargo-tarpaulin with config in tarpaulin.toml.
# Install tarpaulin
cargo install cargo-tarpaulin
# Run coverage with project config (outputs XML + HTML reports)
cargo tarpaulin --config tarpaulin.toml
# Quick terminal summary
cargo tarpaulin --workspace --out Stdout
# Generate HTML report only
cargo tarpaulin --workspace --out Html
# Open tarpaulin-report.html in browserReports: cobertura.xml (for CI/Codecov) and tarpaulin-report.html (for local viewing).
- Clippy:
cargo clippy -- -D warnings(all warnings are errors in CI) - Format:
cargo fmt --checkenforced in CI - Edition: Rust 2024, stable toolchain
- Max line width: 100 characters (rustfmt)
| Disallowed | Use Instead | Reason |
|---|---|---|
std::env::set_var / remove_var |
-- | Data race in multi-threaded context |
std::thread::sleep |
tokio::time::sleep |
Blocks the async runtime |
.unwrap() |
.expect("reason") or ? |
Require descriptive error context |
std::time::Instant |
tokio::time::Instant |
Consistent with tokio runtime |
.unwrap() is allowed in test files via #![allow(clippy::disallowed_methods)].
- Use
anyhow::Resultwith.context()for descriptive errors - Propagate errors with
?, never silently swallow - Validate config at startup (cert paths exist, worker_threads > 0, etc.)
- Cache updates replace the entire
Vecfor a key (never mutate in place) - Config structs are
Clone-- new configs are built, not mutated Arc<DashMap>for concurrent read-heavy access
| Job | What it does |
|---|---|
| Lint | cargo fmt --check + cargo clippy -- -D warnings |
| Build & Test | cargo build + cargo test |
| Benchmarks | cargo bench --no-run (compile check only) |
| Coverage | cargo-tarpaulin -> Codecov (target: 60% project, 70% patch) |
All jobs use dependency caching (Swatinem/rust-cache@v2).
| Test File | Tests | Description |
|---|---|---|
crates/doggy-dns-core/tests/config_test.rs |
13 | Config parsing, defaults, validation (TLS/HTTPS/remote_config) |
crates/doggy-dns-core/tests/handler_test.rs |
7 | Handler pipeline: NoError, NXDomain, ServFail, Refused, middleware callbacks |
crates/doggy-dns-core/tests/middleware_test.rs |
6 | Metrics counter, logging middleware, return values |
crates/doggy-dns-nacos/tests/authority_test.rs |
3 | Nacos authority lookup hit/miss/wrong-zone |
crates/doggy-dns-nacos/tests/mapping_test.rs |
6 | DNS name parsing, record creation |
crates/doggy-dns-nacos/tests/watcher_test.rs |
6 | Instance cache: insert, filter, remove, replace, lowercase keys |
crates/doggy-dns-plugin/tests/authority_chain_test.rs |
9 | Chain resolution: empty, hit, miss, fallthrough, all-skip, Break, error, is_empty |
crates/doggy-dns-plugin/tests/forward_test.rs |
1 | Forward resolver (requires network) |
crates/doggy-dns-plugin/tests/native_test.rs |
1 | Native resolver (requires network) |
tests/integration_test.rs |
2 | End-to-end chain: Nacos hit, Nacos miss -> Forward |
| Bench File | Benchmarks | Description |
|---|---|---|
crates/doggy-dns-core/benches/handler_bench.rs |
2 x 4 | Handler pipeline (nacos hit, nxdomain) x worker_threads 1..4 |
crates/doggy-dns-nacos/benches/nacos_bench.rs |
2 x 4 + 3 | Authority lookup (hit, miss) x worker_threads 1..4 + sync benchmarks |
crates/doggy-dns-plugin/benches/chain_bench.rs |
2 x 4 | Authority chain (empty, nacos hit) x worker_threads 1..4 |
| Library | Purpose |
|---|---|
| hickory-dns | DNS server framework + resolver + protocol types |
| nacos-sdk | Nacos service discovery and config client |
| tokio | Async runtime |
| dashmap | Concurrent hash map for service instance cache |
| rustls | TLS implementation for DoT/DoH |
| tracing | Structured logging |
| criterion | Benchmarking |
- Kubernetes service discovery (
kube-rs/k8s-openapi) - etcd backend (
etcd-rs) - Redis backend (
redis-rs) - Database backend (
sqlx) - Dynamic plugin registry (load plugins at runtime)
- Config hot-reload via Nacos config service (watcher implemented, wiring pending)
- Metrics histogram for query latencies
- Inspired by DNS-F from Alibaba and CoreDNS
- Built on hickory-dns