Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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. |

You can customize the behaviour of `go-hole` by changing domains in the [blacklist](./data/blacklist.txt). The default blacklist can be build with:

Expand Down Expand Up @@ -79,9 +80,10 @@ 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`.

## License

`go-hole` is free software released under the MIT Licence. Please checkout the [LICENSE](./LICENSE) file for details.
`go-hole` is free software released under the MIT Licence. Please checkout the [LICENSE](./LICENSE) file for details.
2 changes: 2 additions & 0 deletions data/override.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mynetwork.local 192.168.1.0
mylocal.local 127.0.0.1
43 changes: 41 additions & 2 deletions dns.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"crypto/tls"
"fmt"
"log"
"strings"
Expand Down Expand Up @@ -38,14 +39,18 @@ 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, logging)
handler := makeDNSHandler(blacklist, upstream, tlsSN, overrides, 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()
Expand All @@ -56,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, 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) {}
Expand All @@ -77,6 +82,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) {
Expand Down Expand Up @@ -153,6 +165,33 @@ func makeDNSHandler(blacklist *Blacklist, upstream string, logging bool) func(dn
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 {
Expand Down
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
4 changes: 4 additions & 0 deletions metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
51 changes: 51 additions & 0 deletions override.go
Original file line number Diff line number Diff line change
@@ -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
}