CIDR query routing (#1159)

* core: allow all CIDR ranges in zone specifications

Allow (e.g.) a v4 reverse on a /17. If a zone is specified in such a
way a FilterFunc is set in the config. This filter is checked against
incoming queries.

For all other queries this adds a 'x != nil' check which will not impact
performace too much. Benchmark function is added as well to check for
this as wel.

Add multiple tests in tests/server_reverse_test.go.

Benchmark shows in the non-reverse case this hardly impact the speed:

~~~
classless:
pkg: github.com/coredns/coredns/core/dnsserver
BenchmarkCoreServeDNS-4   	 1000000	      1431 ns/op	      16 B/op	       1 allocs/op

pkg: github.com/coredns/coredns/core/dnsserver
BenchmarkCoreServeDNS-4   	 1000000	      1429 ns/op	      16 B/op	       1 allocs/op

master:
pkg: github.com/coredns/coredns/core/dnsserver
BenchmarkCoreServeDNS-4   	 1000000	      1412 ns/op	      16 B/op	       1 allocs/op

pkg: github.com/coredns/coredns/core/dnsserver
BenchmarkCoreServeDNS-4   	 1000000	      1429 ns/op	      16 B/op	       1 allocs/op
~~~

* README.md updates
This commit is contained in:
Miek Gieben
2017-10-24 10:16:03 +01:00
committed by GitHub
parent 5f813bcc21
commit fcd0342e42
15 changed files with 269 additions and 120 deletions

View File

@@ -1,6 +1,7 @@
package dnsserver
import (
"net"
"strings"
"github.com/coredns/coredns/plugin"
@@ -11,7 +12,8 @@ import (
type zoneAddr struct {
Zone string
Port string
Transport string // dns, tls or grpc
Transport string // dns, tls or grpc
IPNet *net.IPNet // if reverse zone this hold the IPNet
}
// String return the string representation of z.
@@ -50,7 +52,7 @@ func normalizeZone(str string) (zoneAddr, error) {
str = str[len(TransportGRPC+"://"):]
}
host, port, err := plugin.SplitHostPort(str)
host, port, ipnet, err := plugin.SplitHostPort(str)
if err != nil {
return zoneAddr{}, err
}
@@ -67,7 +69,7 @@ func normalizeZone(str string) (zoneAddr, error) {
}
}
return zoneAddr{Zone: dns.Fqdn(host), Port: port, Transport: trans}, nil
return zoneAddr{Zone: dns.Fqdn(host), Port: port, Transport: trans, IPNet: ipnet}, nil
}
// Supported transports.

View File

@@ -45,8 +45,9 @@ func TestNormalizeZoneReverse(t *testing.T) {
{"10.0.0.0/24.:53", "dns://10.0.0.0/24.:53", false},
// non %8==0 netmasks
{"2003::53/67", "dns://2003::53/67.:53", false},
{"10.0.0.0/25.", "dns://10.0.0.0/25.:53", false},
{"2003::53/67", "dns://0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.3.0.0.2.ip6.arpa.:53", false},
{"10.0.0.0/25.", "dns://10.0.0.0/25.:53", false}, // has dot
{"10.0.0.0/25", "dns://0.0.10.in-addr.arpa.:53", false},
} {
addr, err := normalizeZone(test.input)
actual := addr.String()

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls"
"github.com/coredns/coredns/plugin"
"github.com/mholt/caddy"
)
@@ -29,6 +30,11 @@ type Config struct {
// DNS-over-TLS or DNS-over-gRPC.
Transport string
// If this function is not nil it will be used to further filter access
// to this handler. The primary use is to limit access to a reverse zone
// on a non-octet boundary, i.e. /17
FilterFunc func(string) bool
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
TLSConfig *tls.Config

View File

@@ -1,64 +0,0 @@
package dnsserver
import (
"fmt"
"os"
"strings"
)
// RegisterDevDirective splices name into the list of directives
// immediately before another directive. This function is ONLY
// for plugin development purposes! NEVER use it for a plugin
// that you are not currently building. If before is empty,
// the directive will be appended to the end of the list.
//
// It is imperative that directives execute in the proper
// order, and hard-coding the list of directives guarantees
// a correct, absolute order every time. This function is
// convenient when developing a plugin, but it does not
// guarantee absolute ordering. Multiple plugins registering
// directives with this function will lead to non-
// deterministic builds and buggy software.
//
// Directive names must be lower-cased and unique. Any errors
// here are fatal, and even successful calls print a message
// to stdout as a reminder to use it only in development.
func RegisterDevDirective(name, before string) {
if name == "" {
fmt.Println("[FATAL] Cannot register empty directive name")
os.Exit(1)
}
if strings.ToLower(name) != name {
fmt.Printf("[FATAL] %s: directive name must be lowercase\n", name)
os.Exit(1)
}
for _, dir := range directives {
if dir == name {
fmt.Printf("[FATAL] %s: directive name already exists\n", name)
os.Exit(1)
}
}
if before == "" {
directives = append(directives, name)
} else {
var found bool
for i, dir := range directives {
if dir == before {
directives = append(directives[:i], append([]string{name}, directives[i:]...)...)
found = true
break
}
}
if !found {
fmt.Printf("[FATAL] %s: directive not found\n", before)
os.Exit(1)
}
}
msg := fmt.Sprintf("Registered directive '%s' ", name)
if before == "" {
msg += "at end of list"
} else {
msg += fmt.Sprintf("before '%s'", before)
}
fmt.Printf("[INFO] %s\n", msg)
}

View File

@@ -4,9 +4,11 @@ import (
"flag"
"fmt"
"net"
"strings"
"time"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile"
@@ -66,12 +68,28 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
}
dups[za.String()] = za.String()
// Save the config to our master list, and key it for lookups
// Save the config to our master list, and key it for lookups.
cfg := &Config{
Zone: za.Zone,
Port: za.Port,
Transport: za.Transport,
}
if za.IPNet == nil {
h.saveConfig(za.String(), cfg)
continue
}
ones, bits := za.IPNet.Mask.Size()
if (bits-ones)%8 != 0 { // only do this for non-octet bounderies
cfg.FilterFunc = func(s string) bool {
// TODO(miek): strings.ToLower! Slow and allocates new string.
addr := dnsutil.ExtractAddressFromReverse(strings.ToLower(s))
if addr == "" {
return true
}
return za.IPNet.Contains(net.ParseIP(addr))
}
}
h.saveConfig(za.String(), cfg)
}
}

View File

@@ -40,7 +40,7 @@ type Server struct {
classChaos bool // allow non-INET class queries
}
// NewServer returns a new CoreDNS server and compiles all plugin in to it. By default CH class
// NewServer returns a new CoreDNS server and compiles all plugins in to it. By default CH class
// queries are blocked unless the chaos or proxy is loaded.
func NewServer(addr string, group []*Config) (*Server, error) {
@@ -225,11 +225,22 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
if h, ok := s.zones[string(b[:l])]; ok {
if r.Question[0].Qtype != dns.TypeDS {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
if h.FilterFunc == nil {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
// FilterFunc is set, call it to see if we should use this handler.
// This is given to full query name.
if h.FilterFunc(q) {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
return
}
// The type is DS, keep the handler, but keep on searching as maybe we are serving
// the parent as well and the DS should be routed to it - this will probably *misroute* DS
@@ -244,8 +255,8 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
}
}
if dshandler != nil {
// DS request, and we found a zone, use the handler for the query
if r.Question[0].Qtype == dns.TypeDS && dshandler != nil {
// DS request, and we found a zone, use the handler for the query.
rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r)
if !plugin.ClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)

View File

@@ -18,7 +18,7 @@ func (tp testPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.
func (tp testPlugin) Name() string { return "testplugin" }
func testConfig(transport string) *Config {
func testConfig(transport string, p plugin.Handler) *Config {
c := &Config{
Zone: "example.com.",
Transport: transport,
@@ -27,31 +27,31 @@ func testConfig(transport string) *Config {
Debug: false,
}
c.AddPlugin(func(next plugin.Handler) plugin.Handler { return testPlugin{} })
c.AddPlugin(func(next plugin.Handler) plugin.Handler { return p })
return c
}
func TestNewServer(t *testing.T) {
_, err := NewServer("127.0.0.1:53", []*Config{testConfig("dns")})
_, err := NewServer("127.0.0.1:53", []*Config{testConfig("dns", testPlugin{})})
if err != nil {
t.Errorf("Expected no error for NewServer, got %s.", err)
t.Errorf("Expected no error for NewServer, got %s", err)
}
_, err = NewServergRPC("127.0.0.1:53", []*Config{testConfig("grpc")})
_, err = NewServergRPC("127.0.0.1:53", []*Config{testConfig("grpc", testPlugin{})})
if err != nil {
t.Errorf("Expected no error for NewServergRPC, got %s.", err)
t.Errorf("Expected no error for NewServergRPC, got %s", err)
}
_, err = NewServerTLS("127.0.0.1:53", []*Config{testConfig("tls")})
_, err = NewServerTLS("127.0.0.1:53", []*Config{testConfig("tls", testPlugin{})})
if err != nil {
t.Errorf("Expected no error for NewServerTLS, got %s.", err)
t.Errorf("Expected no error for NewServerTLS, got %s", err)
}
}
func BenchmarkCoreServeDNS(b *testing.B) {
s, err := NewServer("127.0.0.1:53", []*Config{testConfig("dns")})
s, err := NewServer("127.0.0.1:53", []*Config{testConfig("dns", testPlugin{})})
if err != nil {
b.Errorf("Expected no error for NewServer, got %s.", err)
b.Errorf("Expected no error for NewServer, got %s", err)
}
ctx := context.TODO()