Skip to content

Commit

Permalink
feat: Add support for DNS Over TLS.
Browse files Browse the repository at this point in the history
  • Loading branch information
iamd3vil committed Aug 4, 2021
1 parent d4694ce commit e98c1a7
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 29 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
BIN := danse
DIR := dist
GOBIN := go

LAST_COMMIT := $(shell git rev-parse --short HEAD)
LAST_COMMIT_DATE := $(shell git show -s --format=%ci ${LAST_COMMIT})
Expand All @@ -9,7 +10,7 @@ BUILDSTR := ${VERSION} (Commit: ${LAST_COMMIT_DATE} (${LAST_COMMIT}), Build: $(s
.PHONY: build
build:
mkdir -p ${DIR}
CGO_ENABLED=0 go build -o ${DIR}/${BIN} --ldflags="-X 'main.buildString=${BUILDSTR}'"
CGO_ENABLED=0 ${GOBIN} build -o ${DIR}/${BIN} --ldflags="-X 'main.buildString=${BUILDSTR}'"
cp ${DIR}/${BIN} .

run: build
Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Danse

Danse is a DNS resolver which receives packets over conventional DNS(UDP) and resolves it by talking to another resolver over DNS over HTTPS(DoH). DoH would reduce any snooping by ISP or any middlemen since the requests would be encrypted.
Danse is a DNS resolver which receives packets over conventional DNS(UDP) and resolves it by talking to another resolver over DNS over HTTPS(DoH) or DNS Over TLS(DoT). DoH/DoT would reduce any snooping by ISP or any middlemen since the requests would be encrypted.

This would allow any application which doesn't support DoH still use DoH. Danse is supposed to be run locally or on a local network. There is no point running this over internet since DNS queries then wouldn't be encrypted between your device and Danse.
This would allow any application which doesn't support DoH/DoT still use DoH/DoT. Danse is supposed to be run locally or on a local network. There is no point running this over internet since DNS queries then wouldn't be encrypted between your device and Danse.

## Usage

Expand All @@ -16,21 +16,33 @@ Sample config:
# Address for danse to listen on.
bind_address = "127.0.0.1:5454"

# Only used for resolving resolver url. No queries received by danse will be sent here. Default is 9.9.9.9:53
bootstrap_address = "1.1.1.1:53"

# Urls for resolvers.
[resolver]

# Type of resolver. Can be doh or dot.
type = "doh"

# Resolver URLs.
urls = ["https://dns.quad9.net/dns-query", "https://cloudflare-dns.com/dns-query"]


[cache]
# Should the answers be cached according to ttl. Default is true.
cache = true

# Maximum records to cache.
max_items = 10000

# Config for logging
[log]
# Log level
log_level = "info"

# Logs all queries to stdout. Default is false.
log_queries = true

# Only used for resolving resolver url. No queries received by danse will be sent here. Default is 9.9.9.9:53
bootstrap_address = "1.1.1.1:53"

# Urls for resolvers.
[resolver]
urls = ["https://dns.quad9.net/dns-query", "https://cloudflare-dns.com/dns-query"]
```

A sample config file with all the fields can be found at `config.sample.toml`.
Expand All @@ -41,3 +53,4 @@ A sample config file with all the fields can be found at `config.sample.toml`.
- [X] Load Balance to multiple DoH providers for improved privacy
- [X] Option to log queries
- [X] Option to provide a bootstrap DNS server for resolving the given urls
- [X] Support for Dns Over TLS
4 changes: 3 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

type cfgResolver struct {
Type string `koanf:"type"`
Urls []string `koanf:"urls"`
}

Expand All @@ -34,9 +35,10 @@ func initConfig(cfgPath string) (Config, error) {

// Set default values
k.Load(confmap.Provider(map[string]interface{}{
"cache": true,
"cache.cache": true,
"bind_address": "127.0.0.1:53",
"bootstrap_address": "9.9.9.9:53",
"resolver.type": "doh",
}, "."), nil)

if err := k.Load(file.Provider(cfgPath), toml.Parser()); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions config.sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ bootstrap_address = "1.1.1.1:53"

# Urls for resolvers.
[resolver]

# Type of resolver. Can be doh or dot.
type = "doh"

# Resolver URLs.
urls = ["https://dns.quad9.net/dns-query", "https://cloudflare-dns.com/dns-query"]


Expand Down
94 changes: 94 additions & 0 deletions dot_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"log"
"net"
"sync"

"github.com/miekg/dns"
)

type DOTClient struct {
urls []string
conns map[string]*dns.Conn
client *dns.Client

// Last used index
lIndex int

logQueries bool
sync.Mutex
}

func (c *DOTClient) GetDNSResponse(msg *dns.Msg) (*dns.Msg, error) {
c.Lock()

url := c.urls[c.lIndex]

// Increase last index
c.lIndex++

if c.lIndex == len(c.urls) {
c.lIndex = 0
}

c.Unlock()

log.Printf("log queries: %v", c.logQueries)

if c.logQueries {
log.Printf("Sending to %s for query: %s", url, msg.Question[0].String())
}

var r *dns.Msg

makeconn := false
for i := 0; i < 5; i++ {
conn, err := c.getConn(url, makeconn)
if err != nil {
return &dns.Msg{}, err
}

r, _, err = c.client.ExchangeWithConn(msg, conn)
if err != nil {
makeconn = true
continue
}
makeconn = false
break
}
return r, nil
}

func (c *DOTClient) getConn(url string, makeconn bool) (*dns.Conn, error) {
c.Lock()
defer c.Unlock()

if conn, ok := c.conns[url]; ok {
if makeconn {
goto makeConn
}
return conn, nil
}

makeConn:
conn, err := c.client.Dial(url)
if err != nil {
return nil, err
}

c.conns[url] = conn
return conn, nil
}

func NewDOTClient(urls []string, dialer *net.Dialer, logQueries bool) (*DOTClient, error) {
return &DOTClient{
urls: urls,
client: &dns.Client{
Net: "tcp-tls",
Dialer: dialer,
},
conns: map[string]*dns.Conn{},
logQueries: logQueries,
}, nil
}
59 changes: 41 additions & 18 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,18 @@ func main() {
maxCacheItems = cfg.Cache.MaxItems
}

e := env{
cfg: cfg,
}

// Initialize cache
cache, err := lru.New(maxCacheItems)
if err != nil {
log.Fatalln("Couldn't create cache: ", err)
if cfg.Cache.Cache {
cache, err := lru.New(maxCacheItems)
if err != nil {
log.Fatalln("Couldn't create cache: ", err)
}

e.cache = cache
}

// Make a dialer which resolves url with bootstrap address.
Expand All @@ -66,27 +74,30 @@ func main() {
return dialer.DialContext(ctx, network, addr)
}

transport := http.DefaultTransport.(*http.Transport)
transport.DialContext = dialContext
var (
client DNSClient
)

httpClient := &http.Client{
Timeout: 10 * time.Second,
Transport: transport,
}
switch cfg.Resolver.Type {
case "doh":
client, err = getDOHDnsClient(dialContext, cfg)
if err != nil {
log.Fatalf("Couldn't create DNS client: %v", err)
}

dnsClient, err := NewDOHClient(httpClient, cfg.Resolver.Urls, cfg.Log.LogQueries)
if err != nil {
log.Fatalf("error creating doh client: %v", err)
e.client = client
case "dot":
client, err = NewDOTClient(cfg.Resolver.Urls, dialer, cfg.Log.LogQueries)
if err != nil {
log.Fatalf("Couldn't create DNS client: %v", err)
}

e.client = client
}

// Start the DNS server.
dnsServer := &dns.Server{Addr: cfg.BindAddress, Net: "udp"}

e := env{
cache: cache,
cfg: cfg,
client: dnsClient,
}

dns.HandleFunc(".", e.handleDNS)

log.Fatalln(dnsServer.ListenAndServe())
Expand Down Expand Up @@ -170,3 +181,15 @@ func adjustTTL(m *dns.Msg, createdAt time.Time) *dns.Msg {
}
return &adj
}

func getDOHDnsClient(dialContext func(ctx context.Context, network string, addr string) (net.Conn, error), cfg Config) (DNSClient, error) {
transport := http.DefaultTransport.(*http.Transport)
transport.DialContext = dialContext

httpClient := &http.Client{
Timeout: 10 * time.Second,
Transport: transport,
}

return NewDOHClient(httpClient, cfg.Resolver.Urls, cfg.Log.LogQueries)
}

0 comments on commit e98c1a7

Please sign in to comment.