From feabc21864dd8d08b2ceed359169f542ac866e92 Mon Sep 17 00:00:00 2001
From: Eugene Bujak <hmage@hmage.net>
Date: Wed, 28 Nov 2018 16:45:30 +0300
Subject: [PATCH] Unplug coreDNS and plug dnsforward library.

---
 config.go  |  97 +----------------------------------
 coredns.go | 145 ++++++++++++-----------------------------------------
 go.mod     |   1 +
 go.sum     |   2 +
 4 files changed, 38 insertions(+), 207 deletions(-)

diff --git a/config.go b/config.go
index 59cff6bc..5d114538 100644
--- a/config.go
+++ b/config.go
@@ -1,14 +1,11 @@
 package main
 
 import (
-	"bytes"
 	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
-	"regexp"
 	"sync"
-	"text/template"
 	"time"
 
 	"gopkg.in/yaml.v2"
@@ -56,7 +53,7 @@ type coreDNSConfig struct {
 	SafeSearchEnabled   bool     `yaml:"safesearch_enabled"`
 	ParentalEnabled     bool     `yaml:"parental_enabled"`
 	ParentalSensitivity int      `yaml:"parental_sensitivity"`
-	BlockedResponseTTL  int      `yaml:"blocked_response_ttl"`
+	BlockedResponseTTL  uint32   `yaml:"blocked_response_ttl"`
 	QueryLogEnabled     bool     `yaml:"querylog_enabled"`
 	Ratelimit           int      `yaml:"ratelimit"`
 	RefuseAny           bool     `yaml:"refuse_any"`
@@ -188,98 +185,8 @@ func (c *configuration) write() error {
 	return nil
 }
 
-// --------------
-// coredns config
-// --------------
-func writeCoreDNSConfig() error {
-	coreFile := filepath.Join(config.ourBinaryDir, config.CoreDNS.coreFile)
-	log.Printf("Writing DNS config: %s", coreFile)
-	configText, err := generateCoreDNSConfigText()
-	if err != nil {
-		log.Printf("Couldn't generate DNS config: %s", err)
-		return err
-	}
-	err = safeWriteFile(coreFile, []byte(configText))
-	if err != nil {
-		log.Printf("Couldn't save DNS config: %s", err)
-		return err
-	}
-	return nil
-}
-
 func writeAllConfigs() error {
-	err := config.write()
-	if err != nil {
-		log.Printf("Couldn't write our config: %s", err)
-		return err
-	}
-	err = writeCoreDNSConfig()
-	if err != nil {
-		log.Printf("Couldn't write DNS config: %s", err)
-		return err
-	}
-	return nil
-}
-
-const coreDNSConfigTemplate = `.:{{.Port}} {
-	{{if .ProtectionEnabled}}dnsfilter {
-		{{if .SafeBrowsingEnabled}}safebrowsing{{end}}
-		{{if .ParentalEnabled}}parental {{.ParentalSensitivity}}{{end}}
-		{{if .SafeSearchEnabled}}safesearch{{end}}
-		{{if .QueryLogEnabled}}querylog{{end}}
-		blocked_ttl {{.BlockedResponseTTL}}
-		{{if .FilteringEnabled}}{{range .Filters}}{{if and .Enabled .Contents}}
-		filter {{.ID}} "{{.Path}}"
-		{{end}}{{end}}{{end}}
-	}{{end}}
-	{{.Pprof}}
-	{{if .RefuseAny}}refuseany{{end}}
-	{{if gt .Ratelimit 0}}ratelimit {{.Ratelimit}}{{end}}
-	hosts {
-		fallthrough
-	}
-	{{if .UpstreamDNS}}upstream {{range .UpstreamDNS}}{{.}} {{end}} { bootstrap {{.BootstrapDNS}} }{{end}}
-	{{.Cache}}
-	{{.Prometheus}}
-}
-`
-
-var removeEmptyLines = regexp.MustCompile("([\t ]*\n)+")
-
-// generate CoreDNS config text
-func generateCoreDNSConfigText() (string, error) {
-	t, err := template.New("config").Parse(coreDNSConfigTemplate)
-	if err != nil {
-		log.Printf("Couldn't generate DNS config: %s", err)
-		return "", err
-	}
-
-	var configBytes bytes.Buffer
-	temporaryConfig := config.CoreDNS
-
-	// generate temporary filter list, needed to put userfilter in coredns config
-	filters := []filter{}
-
-	// first of all, append the user filter
-	userFilter := userFilter()
-
-	filters = append(filters, userFilter)
-
-	// then go through other filters
-	filters = append(filters, config.Filters...)
-	temporaryConfig.Filters = filters
-
-	// run the template
-	err = t.Execute(&configBytes, &temporaryConfig)
-	if err != nil {
-		log.Printf("Couldn't generate DNS config: %s", err)
-		return "", err
-	}
-	configText := configBytes.String()
-
-	// remove empty lines from generated config
-	configText = removeEmptyLines.ReplaceAllString(configText, "\n")
-	return configText, nil
+	return config.write()
 }
 
 // Set the next filter ID to max(filter.ID) + 1
diff --git a/coredns.go b/coredns.go
index 376e6210..df91b1a6 100644
--- a/coredns.go
+++ b/coredns.go
@@ -3,130 +3,51 @@ package main
 import (
 	"fmt"
 	"log"
-	"os"
-	"path/filepath"
-	"sync" // Include all plugins.
+	"net"
 
-	_ "github.com/AdguardTeam/AdGuardHome/coredns_plugin"
-	_ "github.com/AdguardTeam/AdGuardHome/coredns_plugin/ratelimit"
-	_ "github.com/AdguardTeam/AdGuardHome/coredns_plugin/refuseany"
-	_ "github.com/AdguardTeam/AdGuardHome/upstream"
-	"github.com/coredns/coredns/core/dnsserver"
-	"github.com/coredns/coredns/coremain"
-	_ "github.com/coredns/coredns/plugin/auto"
-	_ "github.com/coredns/coredns/plugin/autopath"
-	_ "github.com/coredns/coredns/plugin/bind"
-	_ "github.com/coredns/coredns/plugin/cache"
-	_ "github.com/coredns/coredns/plugin/chaos"
-	_ "github.com/coredns/coredns/plugin/debug"
-	_ "github.com/coredns/coredns/plugin/dnssec"
-	_ "github.com/coredns/coredns/plugin/dnstap"
-	_ "github.com/coredns/coredns/plugin/erratic"
-	_ "github.com/coredns/coredns/plugin/errors"
-	_ "github.com/coredns/coredns/plugin/file"
-	_ "github.com/coredns/coredns/plugin/forward"
-	_ "github.com/coredns/coredns/plugin/health"
-	_ "github.com/coredns/coredns/plugin/hosts"
-	_ "github.com/coredns/coredns/plugin/loadbalance"
-	_ "github.com/coredns/coredns/plugin/log"
-	_ "github.com/coredns/coredns/plugin/loop"
-	_ "github.com/coredns/coredns/plugin/metadata"
-	_ "github.com/coredns/coredns/plugin/metrics"
-	_ "github.com/coredns/coredns/plugin/nsid"
-	_ "github.com/coredns/coredns/plugin/pprof"
-	_ "github.com/coredns/coredns/plugin/proxy"
-	_ "github.com/coredns/coredns/plugin/reload"
-	_ "github.com/coredns/coredns/plugin/rewrite"
-	_ "github.com/coredns/coredns/plugin/root"
-	_ "github.com/coredns/coredns/plugin/secondary"
-	_ "github.com/coredns/coredns/plugin/template"
-	_ "github.com/coredns/coredns/plugin/tls"
-	_ "github.com/coredns/coredns/plugin/whoami"
-	_ "github.com/mholt/caddy/onevent"
+	"github.com/AdguardTeam/AdGuardHome/dnsforward"
+	"github.com/joomcode/errorx"
 )
 
-// Directives are registered in the order they should be
-// executed.
-//
-// Ordering is VERY important. Every plugin will
-// feel the effects of all other plugin below
-// (after) them during a request, but they must not
-// care what plugin above them are doing.
-
-var directives = []string{
-	"metadata",
-	"tls",
-	"reload",
-	"nsid",
-	"root",
-	"bind",
-	"debug",
-	"health",
-	"pprof",
-	"prometheus",
-	"errors",
-	"log",
-	"refuseany",
-	"ratelimit",
-	"dnsfilter",
-	"dnstap",
-	"chaos",
-	"loadbalance",
-	"cache",
-	"rewrite",
-	"dnssec",
-	"autopath",
-	"template",
-	"hosts",
-	"file",
-	"auto",
-	"secondary",
-	"loop",
-	"forward",
-	"proxy",
-	"upstream",
-	"erratic",
-	"whoami",
-	"on",
-}
-
-func init() {
-	dnsserver.Directives = directives
-}
-
-var (
-	isCoreDNSRunningLock sync.Mutex
-	isCoreDNSRunning     = false
-)
+var dnsServer = dnsforward.Server{}
 
 func isRunning() bool {
-	isCoreDNSRunningLock.Lock()
-	value := isCoreDNSRunning
-	isCoreDNSRunningLock.Unlock()
-	return value
+	return dnsServer.IsRunning()
 }
 
 func startDNSServer() error {
-	isCoreDNSRunningLock.Lock()
-	if isCoreDNSRunning {
-		isCoreDNSRunningLock.Unlock()
+	if isRunning() {
 		return fmt.Errorf("Unable to start coreDNS: Already running")
 	}
-	isCoreDNSRunning = true
-	isCoreDNSRunningLock.Unlock()
 
-	configpath := filepath.Join(config.ourBinaryDir, config.CoreDNS.coreFile)
-	os.Args = os.Args[:1]
-	os.Args = append(os.Args, "-conf")
-	os.Args = append(os.Args, configpath)
-
-	err := writeCoreDNSConfig()
-	if err != nil {
-		errortext := fmt.Errorf("Unable to write coredns config: %s", err)
-		log.Println(errortext)
-		return errortext
+	filters := []dnsforward.Filter{}
+	for _, filter := range config.Filters {
+		filters = append(filters, dnsforward.Filter{
+			ID:    filter.ID,
+			Rules: filter.Rules,
+		})
+	}
+
+	newconfig := dnsforward.ServerConfig{
+		UDPListenAddr: &net.UDPAddr{Port: config.CoreDNS.Port},
+		BlockedTTL:    config.CoreDNS.BlockedResponseTTL,
+		Filters:       filters,
+	}
+
+	for _, u := range config.CoreDNS.UpstreamDNS {
+		upstream, err := dnsforward.GetUpstream(u)
+		if err != nil {
+			log.Printf("Couldn't get upstream: %s", err)
+			// continue, just ignore the upstream
+			continue
+		}
+		newconfig.Upstreams = append(newconfig.Upstreams, upstream)
+	}
+
+	err := dnsServer.Start(&newconfig)
+	if err != nil {
+		return errorx.Decorate(err, "Couldn't start forwarding DNS server")
 	}
 
-	go coremain.Run()
 	return nil
 }
diff --git a/go.mod b/go.mod
index dae96b71..1b8d78e6 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
 	github.com/gobuffalo/packr v1.19.0
 	github.com/google/uuid v1.0.0 // indirect
 	github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
+	github.com/joomcode/errorx v0.1.0
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/mholt/caddy v0.11.0
 	github.com/miekg/dns v1.0.15
diff --git a/go.sum b/go.sum
index 06efaa9e..4ecb93be 100644
--- a/go.sum
+++ b/go.sum
@@ -41,6 +41,8 @@ github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/joomcode/errorx v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc=
+github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
 github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=