Repository navigation
Home
"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
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.
- ip v4 addresses(
A) - ip v6 addresses (
AAAA) - Aliases/Canonical Names (
CNAME) - Mailers (
MX) - Name Servers (
NS) - Service Discovery (
SRV) - Text (
TXT)
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.
@: 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 [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 usingmonitor = ["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
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.
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.
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.
-
host: name of the entry being resolved. -
ipv4: aAentry, denotes an IP version 4 address -
ipv4s: an array of 'ip v4' resolutions -
ipv6: aAAAAentry, denotes and IP version 6 address -
ipv6s: an array of 'ip v6' resolutions
-
host: name of the entry being resolve. -
aliased: the record this record is aliasing
-
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 withtargetandport
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
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 = subIn 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.
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 aAresponse 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'"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: whenfalse, 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
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, whentrue, means that we should not process the current request any further -
Stop, whentrue, has the same meaning, with the additional intent that we should not process any more plugins after this one
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
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.