From 6ebb6ab9c9df2fd29e96cdd14ec1a30a5e36ee40 Mon Sep 17 00:00:00 2001 From: Martin Brugnara Date: Mon, 21 Dec 2020 01:18:23 +0100 Subject: [PATCH 1/4] Add support for upstream DNS over TLS --- README.md | 15 ++++++++------- dns.go | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b6ea3b8..3961a52 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,13 @@ go build `go-hole` can be configured using a few environment variables: -| Environment Variable | Default Value | Function | -| -------------------- | ------------- | ------------------------------------------------------------ | -| `DNS_PORT` | `8053` | UDP port where to listen for DNS queries. | -| `PROMETHEUS_PORT` | `9090` | TCP port where to serve the collected metrics. | -| `UPSTREAM_DNS` | `1.1.1.1:53` | IP and port of the upstream DNS to use to resolve the queries. | -| `DEBUG` | `false` | If true, `go-hole` logs all queries to the standard output. | +| Environment Variable | Default Value | Function | +| ---------------------- | ------------- | --------------------------------------------------------------------- | +| `DNS_PORT` | `53` | UDP port where to listen for DNS queries. | +| `PROMETHEUS_PORT` | `9090` | TCP port where to serve the collected metrics. | +| `UPSTREAM_DNS` | `1.1.1.1:53` | IP and port of the upstream DNS to use to resolve the queries. | +| `UPSTREAM_TLS_SRVNAME` | `` | DNS server name for TLS certificate validation (enables DNS over TLS) | +| `DEBUG` | `false` | If true, `go-hole` logs all queries to the standard output. | You can customize the behaviour of `go-hole` by changing domains in the [blacklist](./data/blacklist.txt). The default blacklist can be build with: @@ -84,4 +85,4 @@ By default, metrics are served over HTTP at port `9090` and path `/metrics`. ## License -`go-hole` is free software released under the MIT Licence. Please checkout the [LICENSE](./LICENSE) file for details. \ No newline at end of file +`go-hole` is free software released under the MIT Licence. Please checkout the [LICENSE](./LICENSE) file for details. diff --git a/dns.go b/dns.go index 63e155b..019c816 100644 --- a/dns.go +++ b/dns.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "fmt" "log" "strings" @@ -40,12 +41,14 @@ func runDNSServer() { // make the custom handler function to reply to DNS queries upstream := getEnvOrDefault("UPSTREAM_DNS", "1.1.1.1:53") + tlsSN := getEnvOrDefault("UPSTREAM_TLS_SRVNAME", "") logging := getEnvOrDefault("DEBUG", "") == "true" - handler := makeDNSHandler(blacklist, upstream, logging) + handler := makeDNSHandler(blacklist, upstream, tlsSN, logging) // start the server port := getEnvOrDefault("DNS_PORT", "53") fmt.Printf("Starting DNS server on UDP port %s (logging = %t)...\n", port, logging) + fmt.Printf("using upstream: %s (TLS: %s)\n", upstream, tlsSN) server := &dns.Server{Addr: ":" + port, Net: "udp"} dns.HandleFunc(".", handler) err := server.ListenAndServe() @@ -56,7 +59,7 @@ func runDNSServer() { // makeDNSHandler creates an handler for the DNS server that caches // results from the upstream DNS and blocks domains in the blacklist. -func makeDNSHandler(blacklist *Blacklist, upstream string, logging bool) func(dns.ResponseWriter, *dns.Msg) { +func makeDNSHandler(blacklist *Blacklist, upstream string, tlsNS string, logging bool) func(dns.ResponseWriter, *dns.Msg) { // create the logger functions logger := func(res *dns.Msg, duration time.Duration, how string) {} @@ -77,6 +80,13 @@ func makeDNSHandler(blacklist *Blacklist, upstream string, logging bool) func(dn // we use a single client to resolve queries against the upstream DNS client := new(dns.Client) + if len(tlsNS) > 0 { + // Inject server name to verify certificate, otherwise we only have ip + client.TLSConfig = new(tls.Config) + client.TLSConfig.ServerName = tlsNS + // Use TLS + client.Net = "tcp-tls" + } // create the real handler return func(w dns.ResponseWriter, req *dns.Msg) { From 327b9de11f12f1dcaa9637a1a653d75dc36e1002 Mon Sep 17 00:00:00 2001 From: Martin Brugnara Date: Mon, 21 Dec 2020 01:25:21 +0100 Subject: [PATCH 2/4] Update dns dep --- go.mod | 5 +---- go.sum | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 8575b17..cffb835 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/gogo/protobuf v1.3.0 // indirect github.com/golang/protobuf v1.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/miekg/dns v1.0.14 + github.com/miekg/dns v1.1.35 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v0.9.0 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect @@ -16,8 +16,5 @@ require ( github.com/spaolacci/murmur3 v0.0.0-20170819071325-9f5d223c6079 // indirect github.com/willf/bitset v1.1.9 // indirect github.com/willf/bloom v2.0.3+incompatible - golang.org/x/crypto v0.0.0-20181029103014-dab2b1051b5d // indirect - golang.org/x/net v0.0.0-20181029044818-c44066c5c816 // indirect golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 // indirect ) diff --git a/go.sum b/go.sum index 161a8f0..10467a3 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY= @@ -28,10 +30,26 @@ github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= golang.org/x/crypto v0.0.0-20181029103014-dab2b1051b5d h1:5JyY8HlzxzYI+qHOOciM8s2lJbIEaefMUdtYt7dRDrg= golang.org/x/crypto v0.0.0-20181029103014-dab2b1051b5d/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 78736c569c59e92eeca266fcbd694900898b387c Mon Sep 17 00:00:00 2001 From: Martin Brugnara Date: Sun, 17 Oct 2021 00:30:51 +0200 Subject: [PATCH 3/4] Add option to disable Prometheus --- metrics.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/metrics.go b/metrics.go index b147fd3..e9c6444 100644 --- a/metrics.go +++ b/metrics.go @@ -16,6 +16,10 @@ var ( // the application metrics in the Prometheus format. func runPrometheusServer() { port := getEnvOrDefault("PROMETHEUS_PORT", "9090") + if port == "0" { + fmt.Printf("HTTP server with metrics has been DISABLED.\n") + return + } fmt.Printf("Starting HTTP server with metrics on TCP port %s...\n", port) server := &http.Server{Addr: "0.0.0.0:" + port} From e548a7881ab4f56a41904967b2181e4483095328 Mon Sep 17 00:00:00 2001 From: Martin Brugnara Date: Sun, 17 Oct 2021 00:31:19 +0200 Subject: [PATCH 4/4] Add support for domain override (A only for now) --- README.md | 3 ++- data/override.txt | 2 ++ dns.go | 33 ++++++++++++++++++++++++++++-- override.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 data/override.txt create mode 100644 override.go diff --git a/README.md b/README.md index 3961a52..71fe56a 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ go build | Environment Variable | Default Value | Function | | ---------------------- | ------------- | --------------------------------------------------------------------- | | `DNS_PORT` | `53` | UDP port where to listen for DNS queries. | -| `PROMETHEUS_PORT` | `9090` | TCP port where to serve the collected metrics. | +| `PROMETHEUS_PORT` | `9090` | TCP port where to serve the collected metrics. Port 0 disables the service. | | `UPSTREAM_DNS` | `1.1.1.1:53` | IP and port of the upstream DNS to use to resolve the queries. | | `UPSTREAM_TLS_SRVNAME` | `` | DNS server name for TLS certificate validation (enables DNS over TLS) | | `DEBUG` | `false` | If true, `go-hole` logs all queries to the standard output. | @@ -80,6 +80,7 @@ By default, `go-hole` does not log any DNS query. Logging can be enabled for deb | Histogram | `gohole_dns_queries_duration_seconds` | Duration of replies to DNS queries. | | Histogram | `gohole_blacklist_lookup_duration_seconds` | Duration of a domain lookup in the blacklist. | | Histogram | `gohole_cache_operation_duration_seconds` | Duration of an operation on the cache. | +| Histogram | `gohole_override_duration_seconds` | Duration of a domain overrided lookup. | By default, metrics are served over HTTP at port `9090` and path `/metrics`. diff --git a/data/override.txt b/data/override.txt new file mode 100644 index 0000000..5200626 --- /dev/null +++ b/data/override.txt @@ -0,0 +1,2 @@ +mynetwork.local 192.168.1.0 +mylocal.local 127.0.0.1 diff --git a/dns.go b/dns.go index 019c816..a8ae060 100644 --- a/dns.go +++ b/dns.go @@ -39,11 +39,13 @@ func runDNSServer() { blacklist := LoadBlacklistOrFail(blacklistPath) fmt.Printf("Loading list of %d blocked domains...\n", blacklist.Size()) + overrides := LoadOverrideListOrFail(overridePath) + // make the custom handler function to reply to DNS queries upstream := getEnvOrDefault("UPSTREAM_DNS", "1.1.1.1:53") tlsSN := getEnvOrDefault("UPSTREAM_TLS_SRVNAME", "") logging := getEnvOrDefault("DEBUG", "") == "true" - handler := makeDNSHandler(blacklist, upstream, tlsSN, logging) + handler := makeDNSHandler(blacklist, upstream, tlsSN, overrides, logging) // start the server port := getEnvOrDefault("DNS_PORT", "53") @@ -59,7 +61,7 @@ func runDNSServer() { // makeDNSHandler creates an handler for the DNS server that caches // results from the upstream DNS and blocks domains in the blacklist. -func makeDNSHandler(blacklist *Blacklist, upstream string, tlsNS string, logging bool) func(dns.ResponseWriter, *dns.Msg) { +func makeDNSHandler(blacklist *Blacklist, upstream string, tlsNS string, overrides map[string]string, logging bool) func(dns.ResponseWriter, *dns.Msg) { // create the logger functions logger := func(res *dns.Msg, duration time.Duration, how string) {} @@ -163,6 +165,33 @@ func makeDNSHandler(blacklist *Blacklist, upstream string, tlsNS string, logging return } + // then, check if the domain is overridden locally + if override, ok := overrides[domain]; ok && queryType == "A" { + //mx, err := dns.NewRR("example.com. 10 IN A " + override) + mx, err := dns.NewRR(domain + ". 10 IN A " + override) + if err != nil { + log.Print("Error to generate the DNS response message for the client", err) + return + } + + res := req.SetReply(req) + req.Question = []dns.Question{query} + res.Answer = []dns.RR{mx} + + // TODO: you probably want to remove this logging + log.Printf(" --> Override response for %s to %s (remote address = %s)", domain, override, w.RemoteAddr()) + err = w.WriteMsg(res) + if err != nil { + errorLogger(err, "Error to write DNS response message to client") + } + + // collect metrics + durationSeconds := time.Since(start).Seconds() + queriesHistogram.WithLabelValues("override", queryType).Observe(durationSeconds) + + return + } + // finally, query an upstream DNS res, rtt, err := client.Exchange(req, upstream) if err == nil { diff --git a/override.go b/override.go new file mode 100644 index 0000000..05727a0 --- /dev/null +++ b/override.go @@ -0,0 +1,51 @@ +package main + +import ( + "bufio" + "io" + "log" + "os" + "strings" +) + +const overridePath = "./data/override.txt" + +func LoadOverrideListOrFail(path string) map[string]string { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + log.Printf("No domain override file found (%s). Continuing with empty set.", path) + return map[string]string{} + } else { + log.Panic(err) + } + } + + // open the file + file, err := os.Open(path) + if err != nil { + log.Panic(err) + } + defer file.Close() + + overrideMap := map[string]string{} + + // read the file line-by-line + // NB: the file MUST be ORDERED and all domains LOWER CASE! + reader := bufio.NewReader(file) + i := 0 + for ; ; i++ { + line, err := reader.ReadString('\n') + if err == io.EOF { + break + } + if err != nil { + log.Panic(err) + } + + // NOTE: expecting "domain ipv4" + tuple := strings.Fields(strings.TrimSpace(line)) + overrideMap[tuple[0]] = tuple[1] + } + log.Printf("Loaded %d entries in override map from %s.", i, path) + return overrideMap +}