Skip to content
Chris F Ravenscroft edited this page Dec 31, 2023 · 5 revisions

"A toy DNS for hobbyists and worried people."

KittenDNS is really fast, really easy to roll out, really portable, and really easy to understand*

* and yet, here we are 😄

Hopefully, this short documentation helps you get started "really quickly"

Table of Content

Features

KittenDNS supports many types of resolutions, and its configuration addresses these varied resources with a very approachable syntax.

This syntax even lets you create rules using English expressions.

If, however, these rules are not enough to customize its behavior, you can use plugins, including a full JavaScript interpreter.

(Currently) supported resolutions

  • ip v4 addresses(A)
  • ip v6 addresses (AAAA)
  • Aliases/Canonical Names (CNAME)
  • Mailers (MX)
  • Name Servers (NS)
  • Service Discovery (SRV)
  • Text (TXT)

Concepts

Canonicalized hosts

A host can be provided, in the configuration, either as a simple name (e.g. server1), or a fully qualified entry (e.g. server1.example.com)

When appropriate, KittenDNS supports simple name definitions and will, under the hood, "canonicalize" them by appending their zone name.

For instance, server1 will become server1.example.com

If a fully qualified entry is provided, it will not be canonicalized.

Special hosts

@: this host entry represents the zone name's resolution itself; for instance, it lets us resolve "example.com" to an IP address

*: a "catch-all" entry that will resolve any otherwise unconfigured hostname in the zone

The Configuration File

Global Configuration

The [settings] section.

  • autoreload: set to true, and KittenDNS will monitor the configuration file for updates and automatically reload its configuration. Note that, in a plugin's configuration, you can add the plugin configuration itself the the monitoring process using monitor = ["name1", "name2", ...]
  • listen: port to listen on (e.g. 53)
  • loadbalance: if a query has multiple responses, KittenDNS will round-robin through them for repeat requests
  • cache: when true, recursive queries are cached for the duration of the responses' specified TTL
  • flatten: when true, local CNAME resolutions will be converted to A responses, eliminating the need for multiple requests
  • lazy: when false, KittenDNS will keep adding responses to a CNAME query until it finds the final record
  • disableruleengine: when true, the rule engine will not be run, for a very modest performance gain
  • parent.address: a recursive parent for non-authoritative responses

Zone configuration

The [[zone]] sections.

  • origin: the dns zone name
  • TTL: default TTL for all records in this zone

A note on TTL:

Time To Live. This tells the processes querying the server that they do not need to check for this record again until this value has expired.

Unfortunately, other name servers may or may not decide to obey this. For instance, some home internet providers will increase the cache duration dramatically.

Authoritative zone section

The [zone.auth] subsection.

This is the "Start of Authority (SOA)" record. It is used to specific the hostmaster's email address, primary name server, and a serial number.

Any time a change is made to the zone definition, the serial number should be updated.

Zone entries

The many [[zone.record]] subections.

Where most of the information pertaining the the zone's mapping is found. The record type will be determined based on which variables are configured.

IP v4 and IP v6 records

  • host: name of the entry being resolved.
  • ipv4: a A entry, denotes an IP version 4 address
  • ipv4s: an array of 'ip v4' resolutions
  • ipv6: a AAAA entry, denotes and IP version 6 address
  • ipv6s: an array of 'ip v6' resolutions

Aliases/Canonical Names

  • host: name of the entry being resolve.
  • aliased: the record this record is aliasing

Service Discovery records

  • srv: service name, e.g. "sip"
  • proto: protocol, e.g. "tcp"
  • priority: the service's priority, where a lower priority response is preferred to a lower priority one
  • weight: If all responses for this service have the same priority, the most highly weighted one wins
  • target: where this service is located (host)
  • port: where this service is located (port)
  • noservice: when true, indicates this service is definitely not available in this zone; incompatible with target and port

Email Servers

The special type [[zone.mailer]] subsections.

  • host: name of the entry being resolved.
  • priority: the email server's priority, where a lower priority response is preferred to a lower priority one
  • nomailer: when true, indicates this zone definitely does not provide email services; incompatible with other mailer definitions

Name Servers

The special type [[zone.nameserver]] subections.

  • host: name of the entry being resolved
  • target: where this name server is located (host)

If only host is specified, this denotes a name server that can authoritatively resolve the records for the current zone.

This can be a host belonging to this zone, or an external nameserver.

If target is specified, it represents a delegated subdomain. For instance:

[[zone]]
origin = "example.com."
    [[zone.nameserver]]
    host = ns1
    target = sub

In this example, we are declaring that requests for hosts in the sub.example.com sub-domain must be directed to ns1.example.com

If you are resolving a domain using KittenDNS, but delegating one of its sub-domains' resolution to other servers, it is important to specify those servers in the domain's zone configuration.

The Rule Engine

Rules use a simple language to provide customized responses.

Each rule is defined in a [[rule]] statement.

Their syntax is a combination of condition and action

Available actions are:

  • rewrite: rewrite <content> will rewrite a A response with the new content
  • drop: do not provide a response
  • inspect: this will cause KittenDNS to display the returned response in its console

Example:

[[rule]]
condition = "remoteip != '192.168.1.19' and host startsWith 'google.'"
action = "rewrite '142.250.189.14'"

Plugins

KittenDNS supports go plugins. In fact, it comes with a couple default plugins:

  • an example plugins, demonstrating how responses can be manipulated, for instance based on the requester's ip address
  • a javascript plugin, that will execute any request/response handling

In the configuration file, supported plugin settings are:

  • enabled: when false, the plugin will not be loaded
  • path: path the the compiled plugin library
  • prehandler: name of the plugin function to invoke when handling a request
  • posthandler: name of the plugin function to invoke when handling a response
  • arguments: some plugins will accept an array of arguments from the configuration file
  • monitor: additional files to monitor, to trigger a reload of the configuration

Plugin invocation

The plugin's prehandler and posthandler functions will be invoked respectively before, and after KittenDNS prepares a response to a request.

They will be invoked with the arguments specified in the arguments variable.

Their signature must be:

ProcessQuery(p PreOrPost, ip string, m *dns.Msg, q *dns.Question) (*Update, error)

They must return a pointer to an Update structure looking like this:

type Update struct {
        Action   Action
        Stop     bool
        Done     bool
        Question *dns.Question
        RR       []dns.RR
}
  • Done, when true, means that we should not process the current request any further
  • Stop, when true, has the same meaning, with the additional intent that we should not process any more plugins after this one

The JavaScript plugin

This plugin registers with KittenDNS to manipulate both requests and responses.

An example configuration block looks like this:

[[plugin]]
enabled = true
path = "./jsscript.so"
prehandler = "JsScriptPreHandler"
posthandler = "JsScriptPostHandler"
arguments = ["special.js"]
monitor = ["special.js"]

The version of JavaScript supported in this plugin is ECMAScript 5.1.

With the plugin enabled, your main/5 function will be invoked twice: before and after KittenDNS' built-in resolution.

The function's signature is:

main(preOrPost, ip, answers, type, name) disposition
  • preOrPost: invoked before or after resolution
  • ip: ip address of the requester
  • answers: if post-resolution, contains our current response information
  • type: request type (typeA, typeAAAA, typeCNAME, typeSOA, typeMX,typeNS, typePTR, typeTXT, typeSRC)

Let's take a look at the returned disposition structure:

  • action: indicates what KittenDNS should do with this information
  • type: returned record type
  • TTL: returned record time to live
  • RR: a DNS record
  • Stop: optional
  • Done: optional

Above, these actions are available:

  • Question: rewrite the request
  • Reply: provide a response, or add one if a response is already available
  • Rewrite: replace the existing response
  • Deny: reject the request

Here is a code sample:

function main(preOrPost, ip, answers, type, name) {
   if (preOrPost == pre)
        return prefn(ip, type, name);
    return {}
}

function prefn(ip, type, name) {
    if (type == typeA && name.endsWith(".nm.example.com.")) {
        return {"action": Reply, "type": type, "TTL": 60, "RR": [{"host": name, "ip": "8.8.8.8"}], "Stop": true};
    }
    return {}
}

This short, contrived example, will, for no obvious reason, reply that any host in the nm.example.com subdomain resolves to 8.8.8.8!

Note how we are pre-empting actual resolution, since we are running this code in the pre stage, and return Stop as true

RFC2136 and LetsEncrypt compatibility

It is possible to run e.g. certbot and use this software to automatically provide the answers that LetsEncrypt needs to validate that it can deliver a certificate for a given domain:

certbot certonly --test-cert -m you@example.com \
    --config-dir . --work-dir . --logs-dir . \
    --dns-rfc2136 \
    --dns-rfc2136-propagation-seconds 5 \
    --dns-rfc2136-credentials=secrets.ini \
    -d yourhost.example.com

Note: remove --test-cert to get a production certificate.

⌂ 换网址