Skip to content

vegertar/dnsrouter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status codecov Go Report Card GoDoc

DnsRouter

DnsRouter is a lightweight high performance DNS request router with chaining middlewares.

Highly inspired by julienschmidt's HttpRouter and miekg's CoreDNS, actually, this router is developed upon HttpRouter's extremely fast radix tree, and passed all test cases of file plugin from CoreDNS.

In contrast to CoreDNS which is a complete DNS server, DnsRouter is targeting a library of a tree of stub name servers, all other resolving functions like filling out ANSWER, AUTHORITY, ADDITIONAL sections are designed as middlewares, which makes name server efficient and flexible.

Features

Named parameters in routing patterns: Directly inheriting parameterized patterns from HttpRouter, includes both :param and *catchAll, anyone who confuses wildcard DNS records or used to conventional HTTP mux patterns would feel easy to use it.

Anonymous parameters in routing patterns: Against named parameters in routing patterns, an anonymous asterisk in the beginning of domain patterns, e.g. *., is interpreted in DNS wildcard semantics, as RFC 4592, which makes DnsRouter compatible with traditional DNS wildcard matching rules.

Multi-Zone in one tree: A router instance could safely contain multiple zones simultaneously, the underlying radix tree promises the best performance if you have lots of records with lots of domains, or zones.

Nearly Zero Garbage: As HttpRouter, the tree related processes generate zero bytes of garbage, the actual up to 4 more heap allocations that are made, is from zone slice (1 alloc), domain name reversing (2 allocs), and a returning of an interface (1 alloc).

Out-of-box stub name server: The builtin middlewares are organized into two schemes, DefaultScheme and SimpleScheme. Use these schemes make DnsRouter working as an out-of-box stub name server, i.e. looking up name records, following CNAME redirection, expanding wildcards, supplying DNSSEC RRset and recovering panic, etc. What the different of DefaultScheme and SimpleScheme is that the later doesn't filling out AUTHORITY and ADDITIONAL sections.

Chaining middlewares: DnsRoute scales well by chaining middlewares, enjoyably choose what you need from builtin middlewares or implement your owns to extend stub name server, e.g. a recursive resolver or cache.

Fast: Benchmarks show DnsRouter is 2x to 4x faster than file plugin of CoreDNS.

Dependencies

Golang 1.9.x and miekg's awesome DNS library.

Install

Using the default go tool commands:

go get -v github.com/vegertar/dnsrouter

Usage

Let's start with a trivial example:

package main

import (
	"context"

	"github.com/miekg/dns"
	"github.com/vegertar/dnsrouter"
)

func main() {
	router := dnsrouter.New()
	router.HandleFunc("local A", func(w dnsrouter.ResponseWriter, req *dnsrouter.Request) {
		lo, err := dns.NewRR("local A 127.0.0.1")
		if err != nil {
			panic(err)
		}

		result := w.Msg()
		result.Answer = append(result.Answer, lo)
	})

	err := dns.ListenAndServe(":10053", "udp", dnsrouter.Classic(context.Background(), router))
	if err != nil {
		panic(err)
	}
}

Tests with dig command and omits unnecessary output, the sample print is shown below.

$ dig @127.0.0.1 -p 10053 local

;; ANSWER SECTION:
local.                  3600    IN      A       127.0.0.1

Then adds a SRV record.

	srv := new(dns.SRV)
	srv.Hdr.Name = "_dns._udp."
	srv.Hdr.Rrtype = dns.TypeSRV
	srv.Hdr.Class = dns.ClassINET
	srv.Port = 10053
	srv.Target = "local."

	router.HandleFunc("local. SRV", func(w dnsrouter.ResponseWriter, req *dnsrouter.Request) {
		result := w.Msg()
		result.Answer = append(result.Answer, srv)
	})

dig with SRV could display the service, with additional A record that we set before as well.

$ dig @127.0.0.1 -p 10053 local SRV

;; ANSWER SECTION:
_dns._udp.              0       IN      SRV     0 0 10053 local.

;; ADDITIONAL SECTION:
local.                  3600    IN      A       127.0.0.1

Alternatively, you could try dig with ANY type.

$ dig @127.0.0.1 -p 10053 local ANY

;; ANSWER SECTION:
local.                  3600    IN      A       127.0.0.1
_dns._udp.              0       IN      SRV     0 0 10053 local.

Wants to known what happens when raises an exception? Adds some lines like below.

	router.HandleFunc("local. SRV", func(w dnsrouter.ResponseWriter, req *dnsrouter.Request) {
		panic("oops: an exception")
	})

dig with SRV option again.

$ dig @127.0.0.1 -p 10053 local. SRV

;; ANSWER SECTION:
_dns._udp.              0       IN      SRV     0 0 10053 local.

;; ADDITIONAL SECTION:
local.                  0       IN      TXT     "panic" "oops: an exception" "main.main.func3:35"

This time the ADDITIONAL section contains a TXT record instead, which describes errors in shortly, includes a flag literal string "panic", an error message, and the trace information.

All above records are writing out by builtin middlewares, but there is no logging middleware to log every incoming DNS queries, let's implement a simple logger in here.

func LoggerHandler(h dnsrouter.Handler) dnsrouter.Handler {
	return dnsrouter.HandlerFunc(func(w dnsrouter.ResponseWriter, req *dnsrouter.Request) {
		since := time.Now()
		q := req.Question[0]

		defer func() {
			log.Printf(`"%s %s %s" %s %v`,
				dns.TypeToString[q.Qtype],
				dns.ClassToString[q.Qclass],
				q.Name,
				dns.RcodeToString[w.Msg().Rcode],
				time.Since(since).String())
		}()

		h.ServeDNS(w, req)
	})
}

Then insert the LoggerHandler into chains.

	router.Middleware = append(router.Middleware, LoggerHandler)
	router.Middleware = append(router.Middleware, dnsrouter.DefaultScheme...)

Running and testing again.

$ go run a.go
2018/02/05 15:57:01 "SRV IN local." SERVFAIL 46.248µs
2018/02/05 15:57:07 "A IN local." NOERROR 139.3µs
2018/02/05 15:57:10 "ANY IN local." SERVFAIL 204.684µs
2018/02/05 15:58:41 "A IN hello." REFUSED 34.333µs

Named parameters & Catch-All parameters

These features are derived from HttpRouter, the only difference is that DnsRouter uses dot ('.') as the label separator, and matches from right to left.

Pattern: :user.example.org.

joe.example.org                 match, captures "joe"
lily.example.org                match, captures "lily"
zuck.mark.example.org           no match
example.org.                    no match
Pattern: *user.example.org.

joe.example.org                 match, captures "joe."
lily.example.org                match, captures "lily."
zuck.mark.example.org           match, captures "zuck.mark."
example.org.                    no match
.example.org.                   match, captures ".", but an illegal domain

Benchmarks

The testing environment is running on Ubuntu-16.04-amd64 with i7-7700HQ CPU @ 2.80GHz. Since all test cases are completely copied from file plugin of CoreDNS, so the bench codes are the same as well.

Without DNSSEC

For DnsRouter:

$ go test -v -benchmem -run=^$ github.com/vegertar/dnsrouter -bench ^BenchmarkLookup$

goos: linux
goarch: amd64
pkg: github.com/vegertar/dnsrouter
BenchmarkLookup/DefaultScheme-8         	  300000	      5842 ns/op	    3264 B/op	      57 allocs/op
BenchmarkLookup/SimpleScheme-8          	  500000	      2824 ns/op	    1632 B/op	      29 allocs/op
PASS
ok  	github.com/vegertar/dnsrouter	3.254s
Success: Benchmarks passed.

For file plugin of CoreDNS:

$ go test -benchmem -run=^$ github.com/coredns/coredns/plugin/file -bench ^BenchmarkFileLookup$

goos: linux
goarch: amd64
pkg: github.com/coredns/coredns/plugin/file
BenchmarkFileLookup-8   	  100000	     14265 ns/op	    6243 B/op	      99 allocs/op
PASS
ok  	github.com/coredns/coredns/plugin/file	1.591s
Success: Benchmarks passed.

With DNSSEC

For DnsRouter:

$ go test -v -benchmem -run=^$ github.com/vegertar/dnsrouter -bench ^BenchmarkLookupDNSSEC$

goos: linux
goarch: amd64
pkg: github.com/vegertar/dnsrouter
BenchmarkLookupDNSSEC-8   	  300000	      5605 ns/op	    3312 B/op	      56 allocs/op
PASS
ok  	github.com/vegertar/dnsrouter	1.747s
Success: Benchmarks passed.

For file plugin of CoreDNS:

$ go test -benchmem -run=^$ github.com/coredns/coredns/plugin/file -bench ^BenchmarkFileLookupDNSSEC$

goos: linux
goarch: amd64
pkg: github.com/coredns/coredns/plugin/file
BenchmarkFileLookupDNSSEC-8   	  100000	     21723 ns/op	    9163 B/op	     232 allocs/op
PASS
ok  	github.com/coredns/coredns/plugin/file	2.419s
Success: Benchmarks passed.

TODO

There are some works in planed:

  • NSEC3
  • 100% code coverage
  • Better examples and docs.

Any contributions are welcome.

About

DnsRouter is a lightweight high performance DNS request router with chaining middlewares

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages