Open-source project
olicesx/kixdns avatar
olicesx/kixdns

KixDNS is a Rust forwarder whose README opens by saying AI wrote it

A DNS forwarder built with Rust.

105 stars13 forksRustGPL-3.0

At a glance

What is it?
An asynchronous, non-recursive DNS forwarder with ordered pipeline rules, two-phase matching, an in-memory cache and hot config reload. Worth reading for the honesty about what its metrics miss: overload SERVFAILs bypass the engine and are never recorded.
Who is it for?
KixDNS fits someone who wants to route queries by rule rather than by upstream, who needs DoH inbound and DoQ outbound on the same process, and who will read the configuration reference rather than expect a turnkey resolver. It does not fit a first DNS deployment: there is no MSRV, the default listener is on every interface, and the response counters are not a delivery record.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • olicesx/kixdns105 ★ · +23
Star history
Repository2026-08-242026-10-22
olicesx/kixdns82105

23 Stars in the last 59 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The README opens by disclosing that AI built the project

The first thing on the page is a notice, not a pitch. This project is entirely built by AI, covering content, documentation and the initial implementation.

That framing matters for how you read everything after it, and the project is consistent about it. There are two READMEs, one in English and one in Simplified Chinese, and the introduction says the document describes the implementation currently on the main branch, with configuration fields that are accepted by deserialization but not used by the running engine called out explicitly. A project that names its own dead configuration keys is telling you something about the rest of its claims.

The subject itself is narrow. KixDNS is an asynchronous, non-recursive DNS forwarding server written in Rust. It accepts queries over UDP and TCP, optionally over inbound DoH, then applies ordered pipeline rules before either forwarding the query or constructing a response.

The word non-recursive is the load-bearing one. A recursive resolver answers from its own cache and walks the hierarchy itself; a forwarder hands the query upstream and lets the upstream do that work. KixDNS is the second thing, with caching in front of it for speed rather than for independence, which is a smaller program with a different set of failure modes.

cargo build, and it comes up listening on 0.0.0.0:5353

Two commands, with a third showing the explicit form:

bash
cargo build --release

# No subcommand: uses config/pipeline.json and the default listener label
./target/release/kixdns

# Explicit run command
./target/release/kixdns run -c config/pipeline.json

The defaults are stated immediately after, and one of them deserves a second look. The default listener addresses are UDP and TCP on `0.0.0.0:5353`. Zero and zero means every interface, so out of the box this is a DNS server reachable from the whole network you are attached to. The default upstream is `1.1.1.1:53`, which is the public resolver, so a fresh install is a very different device from what most people picture when they hear a DNS server.

For a machine you share, narrow that address before the first run rather than after an incident report.

The other stated default is about paths: all paths are passed to the filesystem as written, and relative paths are relative to the process working directory. That matters for `config/pipeline.json`, for the GeoSite and GeoIP files it references, and for anything the DoH server reads, because the answer to "where does it look" is "wherever you started it from".

One build requirement: the repository uses Rust edition 2024 and declares no MSRV, so you need a toolchain that supports edition 2024 and there is no stated minimum version to check against.

Three subcommands, and the listener label is a first-class flag

The whole command line is small enough to read in one screen:

text
kixdns [COMMAND]

COMMANDS:
  run              Run the DNS server
  convert-geo-ip   Convert a GeoIP .dat file to MMDB
  help             Print help

The server options follow:

text
-c, --config <FILE>             Configuration path (default: config/pipeline.json)
    --listener-label <LABEL>    Listener label used by pipeline selectors (default: default)
    --debug                     Enable debug-level logging
    --udp-workers <NUM>         UDP worker count (0 selects available CPU parallelism)
-h, --help                      Show help
-V, --version                   Show version

Two of those flags carry more weight than their position in the list suggests. `--listener-label` is not a cosmetic tag: pipeline selectors match on listener label, so one process can serve several configurations at once and this flag is how you say which one. And `--udp-workers 0` meaning available CPU parallelism means the default worker count follows the host rather than a constant, which is the right default on a dedicated box and the wrong one in a container with a low CPU quota.

The third subcommand is a tool rather than part of the server. `convert-geo-ip` turns a V2Ray GeoIP `.dat` file into MMDB, with `-i` for input, `-o` for output and `-f` taking a comma-separated list of country codes. The V2Ray data format is what the proxy tooling emits, and MaxMind MMDB is what the matchers read, so that converter is the bridge between the two data sources and it is part of the shipped binary rather than a separate utility.

Matching happens twice: request rules, then response rules

The routing model is the substance of the project, and it has two phases rather than one.

A pipeline is selected first, by listener label, client address, domain, query type, QCLASS, EDNS, GeoIP or GeoSite. Matcher chains compose with logical operators named `and`, `or`, `and_not`, `or_not` and `not`, so a rule can require something and exclude something in the same chain rather than nesting blocks.

Then the request rules run. After that, response matching happens too, and response actions cover upstream fallback, filtering, response replacement and response-based pipeline jumps. That last one is the interesting part: a rule can change which pipeline handles the query after seeing the answer, which is what you need for a retry against a different upstream or for blocking a response pattern after the fact.

Transports are chosen by URL prefix rather than by a separate field, which keeps an upstream definition to one line: `udp://`, `tcp://`, `doh://`, `dot://` and `doq://`. Outbound options are UDP, TCP, TCP+UDP hedged requests, DoH per RFC 8484, DoT and DoQ per RFC 9250, while inbound is UDP and TCP with optional DoH requiring a PEM certificate and private key.

EDNS Client Subnet per RFC 7871 appears twice, and the two positions mean different things: there is optional pipeline-level ECS cache isolation, and there is ECS request rewriting at the Forward action.

The dependency list corroborates the features, and shows what is missing

Reading `Cargo.toml` next to the feature list is a quick way to check which claims have code behind them, and the mapping is one to one.

`tokio` and `hickory-proto` carry the async runtime and the DNS wire format. `quinn` with `prost` is the QUIC and message-encoding pair that DoQ needs, which is the dependency that tells you DoQ is real rather than aspirational. `hyper`, `rustls` and `webpki-roots` are the inbound DoH server, with `base64` alongside. `maxminddb` reads the MMDB files and `maxminddb-writer` is what the converter writes them with. `moka` is the in-memory cache, `dashmap` the concurrent state, `arc-swap` the mechanism behind configuration hot reload, and `notify` the file watcher that drives it. `socket2` is there for the worker-aware UDP sockets, and `libc` for the Unix side of that.

On the dev side, `criterion` is the benchmark harness, `rcgen` generates self-signed certificates so the DoH path can be tested, and `tempfile` handles the temporary config.

The absence is the more informative half. There is no recursion library, no authoritative server, no DNSSEC validation and no zone transfer implementation. For a forwarder that is a clean boundary rather than a gap, and it is why the program is small enough to read.

Where a recursive resolver is the alternative, the difference is exactly this: it answers from its own cache and does the walking, which makes it independent of an upstream being up and reachable, and makes it responsible for the whole resolution path.

Config and certificate reload live, but DoQ 0-RTT fallback needs a restart

Three settings look reloadable and only two are, which is the asymmetry the introduction promised to flag.

Configuration does reload. Valid JSON changes are picked up through the file watcher, and invalid changes keep the previous configuration rather than taking the server down. That is the right failure direction for a resolver: a typo in an edit should not empty your cache and stop answering.

The DoH certificate also reloads. Renewed certificate and key PEM files take effect without a restart, and files that fail to load keep the previous certificate in service. So a renewal does not need a coordinated restart, and a truncated file does not take the endpoint down.

DoQ 0-RTT handling is the exception. Its rejection and timeout fallback is a global setting plus per-upstream settings, and the fallback applies until restart. If you tune that in production, the change is not live, and the page does not describe a reload path for it.

GeoIP and GeoSite data is the third reloadable thing, and it is watched too: configured GeoSite files and the configured GeoIP `.dat` or JSON file are reloaded when they change on disk.

So the practical rule for this server is that the routing document and the TLS material are live, the geographic databases are live, and one protocol tuning knob is not. Find that out from the page rather than from a 0-RTT handshake that behaves differently from what you configured.

Your response counters miss the SERVFAILs the listener sends on its own

This is the section to read before you build monitoring on this server, and its honesty is the strongest thing on the page.

Turn it on with `RUST_LOG=warn,kixdns::engine::phases=info`. The `event="dns_response"` record covers one answer produced by the engine, including fast-path, fresh and stale cache, static and singleflight answers. Three exclusions follow, and each one bites.

It is not proof of network delivery. A response the engine produced is not a response that left the machine. Overload SERVFAILs sent directly by the listener when its concurrency permits run out bypass the engine and are not recorded at all, so under load, which is exactly when you want the data, your error count is understated by an unknown amount. Internal background refreshes and intermediate forwarding attempts are excluded as well, and `cache_hit` is true only when the response cache supplied the answer rather than when anything downstream touched it.

So the record is a picture of engine decisions, not of client outcomes. If you are sizing a load test or debugging a resolution failure, that distinction decides whether your numbers mean anything.

Logging itself is plain. Output is text by default with RFC 3339 timestamps in the system's local timezone, and the default filter is error unless `--debug` or `RUST_LOG` changes it. The page also states that the code does not configure JSON log output, even though the `tracing-subscriber` dependency is declared with its json feature. The feature is there; the wiring is not.

The manifest says 0.1.0 and points at a different GitHub organisation

Two fields in `Cargo.toml` disagree with the repository around it.

The package version is `0.1.0`. The newest release is v0.2.0, published on 2026-09-14, after v0.1.1 on 2026-07-27 and v0.1.0 on 2026-05-05. So the source tree you clone reports a version behind the release you would install, and `--version` will not agree with the tag you downloaded.

The `repository` field points at `https://github.com/kixdns/kixdns`, a different organisation from the one hosting this repository, and there is no homepage field at all. If you are evaluating this as an upstream dependency, the question of where to file an issue is not answered by the manifest.

Licence is GPL-3.0, recorded in a `LICENSE` file, which matters more here than for most infrastructure tools because this is software you run as a service rather than a library you link.

One more stated boundary, and it is a good one to end on. Unix builds create separate UDP sockets for workers using the platform's reuse-port support, while non-Unix builds use one UDP socket shared by the workers. The page labels that an implementation detail and not a throughput guarantee, which is the correct way to say that one platform gets a socket layout you have not benchmarked. The last push was on 2026-09-28.

Editorial conclusion

KixDNS fits someone who wants to route queries by rule rather than by upstream, who needs DoH inbound and DoQ outbound on the same process, and who will read the configuration reference rather than expect a turnkey resolver. It does not fit a first DNS deployment: there is no MSRV, the default listener is on every interface, and the response counters are not a delivery record. Verify four things before deploying it: bind the listener to something narrower than `0.0.0.0`, confirm your toolchain supports Rust edition 2024, read the list of configuration fields that deserialise but are unused by the engine, and decide whether you can live without JSON log output, since the README says the code does not configure it. Licence is GPL-3.0, v0.2.0 shipped on 2026-09-14 and the last push was on 2026-09-28.

Frequently asked questions

How do I build and run KixDNS?

Run cargo build --release, then start it with ./target/release/kixdns and no subcommand, which uses config/pipeline.json and the default listener label, or use ./target/release/kixdns run -c config/pipeline.json explicitly. The repository uses Rust edition 2024 and declares no MSRV.

What does KixDNS listen on by default?

UDP and TCP on 0.0.0.0:5353, with 1.1.1.1:53 as the default upstream. All paths are passed to the filesystem as written, so relative paths resolve against the process working directory rather than the configuration file.

Can KixDNS reload configuration and certificates without a restart?

Valid JSON configuration changes reload through the file watcher while invalid changes keep the previous configuration, and renewed DoH certificate and key PEM files take effect without a restart. DoQ 0-RTT rejection and timeout fallback is the exception, applying until restart.

Which DNS transports does KixDNS support?

Inbound DNS over UDP and TCP, optionally DoH per RFC 8484 with a PEM certificate and private key. Outbound it offers UDP, TCP, TCP+UDP hedged requests, DoH, DoT and DoQ per RFC 9250, selected by the udp://, tcp://, doh://, dot:// and doq:// prefixes.

Was KixDNS written by AI?

The page states that the project is entirely built by AI, covering content, documentation and the initial implementation. Licence is GPL-3.0, and the newest release is v0.2.0 from 2026-09-14.

Official sources

  1. Official README
  2. Project repository
  3. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/olicesx-kixdns.svg)](https://hysenlabs.com/projects/olicesx-kixdns)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/olicesx-kixdns.svg)](https://hysenlabs.com/projects/olicesx-kixdns)

Was this useful?