Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c37ca7a
feat(lookup): enhance lookup functionality with source IP handling an…
metah3m Jun 3, 2026
47904b0
feat: add GeoIP support for geo-routing
metah3m Jun 3, 2026
ce3d1ce
feat: implement OCSP auto-refresh for TLS certificate validation and …
metah3m Jun 8, 2026
25f0b23
feat: check if it is on the blacklist during lookup
metah3m Jun 11, 2026
d77d610
Add AWS deployment and Redis read-write separation
metah3m Jun 11, 2026
18d0fdc
feat add endpoint publisher helpers
metah3m Jun 15, 2026
307ccfb
refactor: remove dead code
metah3m Jun 15, 2026
4befa4b
feat: add h3x resolver publishing
metah3m Jun 15, 2026
2228f90
refactor: split ddns backends from facade modules
eareimu Jun 16, 2026
1dd4ed5
chore: remove legacy ddns files
eareimu Jun 16, 2026
965bc9c
refactor: split h3 resolver internals
eareimu Jun 16, 2026
877db8c
refactor: split h3 resolver errors by operation
eareimu Jun 16, 2026
5ad32b2
refactor: sign h3 publish requests from endpoint authority
eareimu Jun 16, 2026
abfdf6c
fix: select one h3 dns endpoint group
eareimu Jun 16, 2026
33ee731
refactor: extract h3 lookup cache
eareimu Jun 16, 2026
22118e3
refactor: rename resolver aggregate error
eareimu Jun 16, 2026
d368dc8
refactor: build unsigned endpoint dns packets
eareimu Jun 16, 2026
9ce4d5f
feat: add scoped dns publisher
eareimu Jun 16, 2026
1d87092
feat: aggregate dns publishers
eareimu Jun 16, 2026
4a7295a
refactor: publish endpoint addresses through publishers
eareimu Jun 16, 2026
f603b1f
docs: update publisher facade README
eareimu Jun 16, 2026
11124f7
refactor: keep publisher items before tests
eareimu Jun 16, 2026
9dcac74
fix: gate query example behind h3 feature
eareimu Jun 16, 2026
0d68334
refactor: group endpoints by certificate chain key
eareimu Jun 16, 2026
081f3b1
fix: gate backend dependencies by feature
eareimu Jun 16, 2026
c5d2a82
fix: target v2 dns api routes
eareimu Jun 16, 2026
c5af4d1
release: prepare v0.4.0
eareimu Jun 16, 2026
e6a4c10
release: converge upstream dependencies
eareimu Jun 16, 2026
96f8f19
fix: gate resolver endpoint grouping helper
eareimu Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[env]
DHTTP_H3_DNS_SERVER = "https://dns.genmeta.net:4433/"
DHTTP_HTTP_DNS_SERVER = "https://dns.genmeta.net/"
DHTTP_MDNS_SERVICE = "_dhttp.local"
DHTTP_STUN_SERVER = "stun.genmeta.net:20002"
DHTTP_ROOT_CA = { value = "intermediate/intermediate.crt", relative = true }
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Cargo.lock
*.log
build

.DS_Store
.vscode/
/geoip
/docs/superpowers
/certs
63 changes: 21 additions & 42 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
[package]
name = "dyns"
description = "DNS discovery and resolver support for DHTTP applications"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/genmeta/ddns"
readme = "README.md"
keywords = ["dhttp", "dns", "mdns", "http3", "quic"]
categories = ["network-programming", "asynchronous"]
autoexamples = false
autobins = false

[lib]
name = "ddns"
Expand All @@ -17,12 +18,12 @@ name = "ddns"
base64 = "0.22"
bitfield-struct = "0.13"
bytes = "1"
dashmap = "6"
dhttp-identity = "0.1.0"
dashmap = { version = "6", optional = true }
dhttp-identity = "0.2.0"
dquic = "0.5.1"
flume = "0.12"
flume = { version = "0.12", optional = true }
futures = "0.3"
libc = "0.2"
libc = { version = "0.2", optional = true }
nom = "8"
rand = "0.10"
ring = "0.17"
Expand All @@ -33,7 +34,7 @@ rustls = { version = "0.23", default-features = false, features = [
rustls-native-certs = { version = "0.8", optional = true }
rustls-pemfile = "2"
snafu = "0.9"
socket2 = { version = "0.6", features = ["all"] }
socket2 = { version = "0.6", features = ["all"], optional = true }
tokio = { version = "1", features = [
"time",
"macros",
Expand All @@ -44,9 +45,9 @@ tokio = { version = "1", features = [
"io-util",
] }
tracing = "0.1"
x509-parser = "0.18"
x509-parser = { version = "0.18", features = ["verify"] }

h3x = { version = "0.3.1", default-features = false, optional = true }
h3x = { version = "0.4.0", default-features = false, optional = true }
http = { version = "1", optional = true }
http-body = { version = "1", optional = true }
http-body-util = { version = "0.1", optional = true }
Expand All @@ -60,67 +61,45 @@ reqwest = { version = "0.13", default-features = false, features = [
], optional = true }
url = { version = "2", optional = true }

clap = { version = "4", features = ["derive"], optional = true }
deadpool-redis = { version = "0.23", optional = true }
idna = { version = "1", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
toml = { version = "1", optional = true }
tower-service = { version = "0.3", optional = true }
tracing-subscriber = { version = "0.3", features = [
"env-filter",
], optional = true }

[features]
default = []
h3x-resolver = [
resolvers = []
publishers = []
dquic-network = ["dep:h3x", "h3x/dquic"]
h3 = [
"dep:dashmap",
"dep:h3x",
"h3x/dquic",
"h3x/hyper",
"dep:http",
"dep:http-body",
"dep:http-body-util",
"dep:url",
]
mdns-resolver = ["dep:h3x", "h3x/dquic"]
http-resolver = ["dep:reqwest", "dep:rustls-native-certs"]
server = [
"h3x-resolver",
"dep:clap",
"dep:deadpool-redis",
"dep:idna",
"dep:serde",
"dep:toml",
"dep:tower-service",
"dep:tracing-subscriber",
]
http = ["dep:dashmap", "dep:reqwest", "dep:rustls-native-certs"]
mdns = ["dep:dashmap", "dep:flume", "dep:libc", "dep:socket2"]

[dev-dependencies]
clap = { version = "4", features = ["derive"] }
h3x = { version = "0.3.1", default-features = false, features = [
"dquic",
] }
h3x = { version = "0.4.0", default-features = false, features = ["dquic"] }
shellexpand = "3"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[[bin]]
name = "ddns-server"
path = "src/bin/ddns-server/main.rs"
required-features = ["server"]

[[example]]
name = "mdns_discover"
path = "examples/mdns_discover.rs"
required-features = ["mdns"]

[[example]]
name = "mdns_query"
path = "examples/mdns_query.rs"
required-features = ["mdns"]

[[example]]
name = "publish"
path = "examples/publish.rs"
required-features = ["h3x-resolver"]
required-features = ["h3"]

[[example]]
name = "query"
path = "examples/query.rs"
required-features = ["h3x-resolver"]
required-features = ["h3"]
187 changes: 37 additions & 150 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,68 @@
# DDNS

`ddns` provides DNS discovery and resolver support for DHTTP applications. It is a
single Rust package: the historical `ddns-core`, `gmdns`, `ddns`, and
`ddns-server` crate boundaries now live as modules and feature-gated targets in
one published Cargo package named `dyns`, with a library target kept as `ddns`
for source compatibility.
`ddns` provides DNS discovery and resolver support for DHTTP applications.
The published Cargo package is `dyns`, and the library target remains `ddns`.

`ddns` exposes backend implementations in `ddns::h3`, `ddns::http`, and `ddns::mdns`,
while `ddns::resolvers` and `ddns::publishers` act as facades for re-exports and
aggregate helper types.

```toml
ddns = { package = "dyns", version = "0.4.0" }
```

## Crate layout

| Module / target | Role |
| Module | Role |
| --- | --- |
| `ddns::core` | DNS packet parser, resource-record types, endpoint `E` record encoding, and HTTP multi-record response wire format. |
| `ddns::mdns` | RFC 6762 multicast DNS transport, LAN publisher, and LAN resolver support. |
| `ddns::resolvers` | Resolver chain plus optional System, mDNS, DNS-over-H3, and DNS-over-HTTP resolvers. |
| `ddns::publisher` | Feature-gated endpoint record signing and publishing loop helpers for DHTTP endpoints. |
| `ddns-server` | DNS-over-H3 publish/lookup server binary, enabled by the `server` feature. |

`ddns` is endpoint-facing support code for the DHTTP ecosystem. Applications
normally reach it through the `dhttp` endpoint facade; lower-level consumers can
depend on package `dyns` directly (typically renamed locally to `ddns`) when
they need DNS wire types, resolver composition, mDNS, or the DNS-over-H3 server.

```toml
ddns = { package = "dyns", version = "0.3.0" }
```
| `ddns::h3` | DNS-over-HTTP/3 backend implementation. |
| `ddns::http` | DNS-over-HTTP backend implementation. |
| `ddns::mdns` | RFC 6762 multicast DNS transport plus LAN resolver/publisher backend implementation. |
| `ddns::resolvers` | Resolver facade: backend re-exports, resolver chains, and `Resolvers` aggregation. |
| `ddns::publishers` | Publisher facade: backend re-exports, scoped publisher atoms, `Publishers` aggregation, and endpoint publication helpers. |

## Features

All optional integrations are feature-gated; the default feature set is empty.
The default feature set is empty.

| Feature | Enables |
| --- | --- |
| `h3x-resolver` | DNS-over-H3 resolver and publisher using `h3x`/`dquic`. |
| `mdns-resolver` | mDNS resolver integration backed by an existing `h3x::dquic::Network`. |
| `http-resolver` | DNS-over-HTTP resolver/publisher using `reqwest` and native roots. |
| `server` | `ddns-server`, Redis storage support, TOML config parsing, and tracing setup. |
| `resolvers` | Resolver aggregation types such as `Resolvers`, `ResolversBuilder`, and `DnsScheme`. |
| `publishers` | Scoped publication helpers such as `Publisher`, `Publishers`, `PublishScope`, `EndpointPublicationLoop`, and `PublishAddresses`; backend `Publish` implementations own any required signing. |
| `dquic-network` | `h3x`/`dquic` network-backed publication helpers such as `EndpointBindingAddresses`; meaningful together with `publishers`, and also used by mDNS resolver aggregation. |
| `h3` | DNS-over-HTTP/3 backend surface (`ddns::h3`, plus `H3Resolver` / `H3Publisher` re-exports from the facades). |
| `http` | DNS-over-HTTP backend surface (`ddns::http`, plus `HttpResolver` / `HttpPublisher` re-exports from the facades). |
| `mdns` | mDNS backend surface (`ddns::mdns`, plus `MdnsResolver` / `MdnsPublisher` re-exports from the facades). |

Backend types live under the `resolvers` / `publishers` facades whenever their backend feature is enabled.
The aggregate `Resolvers` and endpoint-publication helper types are separately gated by the
`resolvers` and `publishers` features.

## Bootstrap constants

`build.rs` generates the resolver defaults exposed from `ddns::resolvers`:
`build.rs` generates resolver defaults exposed from `ddns::resolvers`:

| Environment variable | Public constant | Fallback when unset |
| --- | --- | --- |
| `DHTTP_H3_DNS_SERVER` | `DHTTP_H3_DNS_SERVER` | `https://dhttp.example.net` |
| `DHTTP_HTTP_DNS_SERVER` | `DHTTP_HTTP_DNS_SERVER` | `https://dhttp.example.net` |
| `DHTTP_MDNS_SERVICE` | `DHTTP_MDNS_SERVICE` | `dhttp.example.net` |

The fallbacks are docs/build placeholders, not operational defaults. Real
endpoint, server, and E2E runs should set the DHTTP bootstrap environment before
building.
The fallbacks are docs/build placeholders, not operational defaults.

## Quick start

### Resolver chain

`Resolvers` queries all configured resolvers and streams endpoint addresses from
successful backends. System DNS is always available; mDNS, H3, and HTTP builders
appear behind their features.
Enable the resolver aggregation surface and build a chain explicitly:

```rust
use ddns::resolvers::Resolvers;
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), ddns::resolvers::DnsErrors> {
async fn main() -> Result<(), ddns::resolvers::ResolversError> {
let resolvers = Resolvers::builder().system().build();
let mut endpoints = resolvers.lookup("demo.example.dhttp.net").await?;

Expand Down Expand Up @@ -98,21 +97,13 @@ async fn main() -> std::io::Result<()> {
}
```

Runnable examples live in `examples/`:

```bash
cargo run --example mdns_discover -- --ip 127.0.0.1 --device lo0
cargo run --example mdns_query -- --ip 192.168.5.156 --device en0
```

### DNS-over-H3 examples
Runnable examples:

```bash
cargo run --example query --features h3x-resolver -- \
--server-ca /path/to/root.crt \
--host nat.genmeta.net

cargo run --example publish --features h3x-resolver -- \
cargo run --example mdns_discover --features mdns -- --ip 127.0.0.1 --device lo0
cargo run --example mdns_query --features mdns -- --ip 192.168.5.156 --device en0
cargo run --example query -- --server-ca /path/to/root.crt --host nat.genmeta.net
cargo run --example publish --features h3 -- \
--server-ca /path/to/root.crt \
--client-name demo.example.dhttp.net \
--client-cert /path/to/demo.example.dhttp.net.pem \
Expand All @@ -121,108 +112,4 @@ cargo run --example publish --features h3x-resolver -- \
--addr 192.168.1.100:8080,192.168.1.101:8080
```

See [`examples/README.md`](examples/README.md) for the example CLI parameters
and response decoding notes.

## DNS-over-H3 server

Start the server with the `server` feature:

```bash
cargo run --bin ddns-server --features server -- --config server.toml
```

The server exposes two HTTP/3 routes:

| Route | Meaning |
| --- | --- |
| `POST /publish?host=<name>` | Publish a DNS packet for `host`. Client mTLS is required. |
| `GET /lookup?host=<name>[&limit=N]` | Look up active records for `host`; `limit` caps newest-first dynamic records. |

Lookup responses use header `x-record-format: multi` and the binary body from
`ddns::core::wire::MultiResponse`:

```text
u32 count
repeated count times:
u32 dns_len | dns packet bytes | u32 cert_len | DER publisher certificate bytes
```

Server configuration lives in `server.toml`:

- storage is in-memory by default, or Redis when `redis = "redis://..."` is set;
- `ttl_secs` controls dynamic record expiry;
- `require_signature` controls signed endpoint-record enforcement for Standard
domains;
- `domain_policies` are matched in order, with unlisted domains using the
Standard policy;
- `seed_records` add static bootstrap endpoints to lookup results.

Domain policies:

| Policy | Behavior |
| --- | --- |
| `standard` | Client certificate DNS SAN must match the published host; signed `E` records are required when `require_signature = true`; each certificate fingerprint owns one active record for the host. |
| `open_multi` | Any authenticated client certificate may publish; signature checks are skipped; multiple certificate fingerprints can coexist and lookup returns newest-first records. |

Public DHTTP identity hostnames should use the canonical `DhttpName::SUFFIX`
(`.dhttp.net`). Infrastructure names such as `nat.genmeta.net` can remain under
Genmeta infrastructure domains.

## Endpoint `E` records

Custom DNS record type `E` (`QTYPE = 266`) carries DHTTP endpoint addresses. The
current wire format is:

```text
flags(u8)
[sequence(varint) if CLUSTERED]
primary address: port(u16) + IPv4/IPv6 bytes
[agent address if NAT]
[load(f32) if LOAD]
[signature: scheme(u16) + len(varint) + bytes if SIGNED]
```

Flag bits:

| Bit mask | Name | Meaning |
| --- | --- | --- |
| `0x80` | `FAMILY` | `0` = IPv4, `1` = IPv6. |
| `0x40` | `MAIN` | Primary endpoint for the name. |
| `0x20` | `CLUSTERED` | Sequence number is present; multiple publishers share the name. |
| `0x10` | `NAT` | Agent address is present for NAT traversal. |
| `0x08` | `LOAD` | One-minute load value is present. |
| `0x01` | `SIGNED` | Signature with explicit TLS signature scheme is present. |

For DHTTP endpoint publishing, `MAIN` and `sequence` are derived from the
publisher certificate's DHTTP subject key identifier. Operators do not choose
these fields manually: `primary` certificates publish `MAIN = true`,
`secondary` certificates publish `MAIN = false`, and the certificate-chain
sequence becomes the normalized endpoint-record sequence. An omitted sequence
field means sequence `0`.

Signed records encode the signature scheme in the record; the no-scheme signed
format is not accepted. Legacy unsigned fixed-length endpoint address records are
still parsed by length for address-only compatibility.

## Project structure

```text
src/core.rs DNS core module root
src/core/parser/ DNS packet, name, question, record, varint, and signature parsers
src/core/parser/record/ A/AAAA/SRV/TXT/PTR/CNAME/E record parsing and encoding
src/core/wire.rs HTTP multi-record response wire format
src/mdns.rs mDNS module root
src/mdns/protocol.rs UDP multicast socket and packet routing
src/mdns/service.rs High-level mDNS service API
src/mdns/resolvers/ mDNS resolver integration
src/resolvers.rs Resolver chain and resolver defaults
src/resolvers/h3.rs DNS-over-H3 resolver/publisher
src/resolvers/http.rs DNS-over-HTTP resolver/publisher
src/resolvers/deferred.rs Deferred resolver initialization helper
src/publisher.rs Endpoint record signer and publication loop
src/publisher/ Address selection, publish dispatch, packet signing
src/bin/ddns-server/ DNS-over-H3 server implementation
examples/ mDNS and DNS-over-H3 example programs
server.toml Example server configuration
```
See [`examples/README.md`](examples/README.md) for example CLI parameters and response decoding notes.
Loading
Loading