Add support for DNS stamps and for DNSCrypt servers

This allows connections to DoH servers using their stamp, and adds
support for DNSCrypt servers by the way.
This commit is contained in:
Frank Denis 2021-04-24 17:27:43 +02:00 committed by Karan Sharma
parent 48fd5bb792
commit 6d2eae4f58
9 changed files with 139 additions and 12 deletions

View file

@ -12,7 +12,7 @@
---
**doggo** is a modern command-line DNS client (like _dig_) written in Golang. It outputs information in a neat concise manner and supports protocols like DoH, DoT as well.
**doggo** is a modern command-line DNS client (like _dig_) written in Golang. It outputs information in a neat concise manner and supports protocols like DoH, DoT and DNSCrypt as well.
It's totally inspired from [dog](https://github.com/ogham/dog/) which is written in Rust. I wanted to add some features to it but since I don't know Rust, I found it as a nice opportunity
to experiment with writing a DNS Client from scratch in `Go` myself. Hence the name `dog` +`go` => **doggo**.
@ -195,6 +195,8 @@ URL scheme of the server is used to identify which resolver to use for lookups.
@tcp:// eg: @1.1.1.1 initiates a TCP resolver for 1.1.1.1:53.
@https:// eg: @https://cloudflare-dns.com/dns-query initiates a DOH resolver for Cloudflare DoH server.
@tls:// eg: @1.1.1.1 initiates a DoT resolver for 1.1.1.1:853.
@sdns:// eg: @sdns://AgcAAAAAAAAABzEuMC4wLjEAEmRucy5jbG91ZGZsYXJlLmNvbQovZG5zLXF1ZXJ5
initiates a DNSCrypt or DoH resolver using its DNS stamp
```
### Query Options

View file

@ -36,6 +36,7 @@ var appHelpTextTemplate = `{{ "NAME" | color "" "heading" }}:
{{"@tcp://" | color "yellow" ""}} eg: @tcp://1.1.1.1 initiates a {{"TCP" | color "cyan" ""}} resolver for 1.1.1.1:53.
{{"@https://" | color "yellow" ""}} eg: @https://cloudflare-dns.com/dns-query initiates a {{"DOH" | color "cyan" ""}} resolver for Cloudflare DoH server.
{{"@tls://" | color "yellow" ""}} eg: @tls://1.1.1.1 initiates a {{"DoT" | color "cyan" ""}} resolver for 1.1.1.1:853.
{{"@sdns://" | color "yellow" ""}} initiates a {{"DNSCrypt" | color "cyan" ""}} or {{"DoH" | color "cyan" ""}} resolver using its DNS stamp.
{{ "Query Options" | color "" "heading" }}:
{{"-q, --query=HOSTNAME" | color "yellow" ""}} Hostname to query the DNS records for (eg {{"mrkaran.dev" | color "cyan" ""}}).

View file

@ -12,7 +12,7 @@ complete -c doggo -s 'n' -l 'nameserver' -d "Address of a specific nameserver to
complete -c doggo -s 'c' -l 'class' -d "Network class of the DNS record being queried" -x -a "IN CH HS"
# Transport options
complete -c doggo -x -a "@udp:// @tcp:// @https:// @tls://" -d "Select the protocol for resolving queries"
complete -c doggo -x -a "@udp:// @tcp:// @https:// @tls:// @sdns://" -d "Select the protocol for resolving queries"
# Resolver options
complete -c doggo -l 'ndots' -d "Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise"

3
go.mod
View file

@ -3,9 +3,10 @@ module github.com/mr-karan/doggo
go 1.16
require (
github.com/ameshkov/dnscrypt v1.1.0
github.com/ameshkov/dnsstamps v1.0.3
github.com/fatih/color v1.10.0
github.com/go-chi/chi v1.5.3
github.com/go-chi/render v1.0.1
github.com/knadh/koanf v0.14.0
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/miekg/dns v1.1.35

20
go.sum
View file

@ -1,4 +1,14 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
github.com/ameshkov/dnscrypt v1.1.0 h1:2vAt5dD6ZmqlAxEAfzRcLBnkvdf8NI46Kn9InSwQbSI=
github.com/ameshkov/dnscrypt v1.1.0/go.mod h1:ikduAxNLCTEfd1AaCgpIA5TgroIVQ8JY3Vb095fiFJg=
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -9,8 +19,6 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-chi/chi v1.5.3 h1:+DVDS9/D3MTbEu3WrrH3oz9oP6PlSPSNj8LLw3X17yU=
github.com/go-chi/chi v1.5.3/go.mod h1:Q8xfe6s3fjZyMr8ZTv5jL+vxhVaFyCq2s+RvSfzTD0E=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
@ -24,6 +32,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
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/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
@ -45,13 +54,15 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -61,6 +72,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -5,6 +5,7 @@ import (
"net"
"net/url"
"github.com/ameshkov/dnsstamps"
"github.com/mr-karan/doggo/pkg/config"
"github.com/mr-karan/doggo/pkg/models"
)
@ -60,6 +61,23 @@ func initNameserver(n string) (models.Nameserver, error) {
if err != nil {
return ns, err
}
if u.Scheme == "sdns" {
stamp, err := dnsstamps.NewServerStampFromString(n)
if err != nil {
return ns, err
}
switch stamp.Proto {
case dnsstamps.StampProtoTypeDoH:
ns.Type = models.DOHResolver
address := url.URL{Scheme: "https", Host: stamp.ProviderName, Path: stamp.Path}
ns.Address = address.String()
case dnsstamps.StampProtoTypeDNSCrypt:
ns.Type = models.DNSCryptResolver
ns.Address = n
default:
return ns, fmt.Errorf("unsupported protocol: %v", stamp.Proto.String())
}
}
if u.Scheme == "https" {
ns.Type = models.DOHResolver
ns.Address = u.String()

View file

@ -13,6 +13,7 @@ const (
DOHResolver = "doh"
TCPResolver = "tcp"
DOTResolver = "dot"
DNSCryptResolver = "dnscrypt"
)
// QueryFlags is used store the query params

81
pkg/resolvers/dnscrypt.go Normal file
View file

@ -0,0 +1,81 @@
package resolvers
import (
"github.com/ameshkov/dnscrypt"
"github.com/miekg/dns"
"github.com/sirupsen/logrus"
)
// DNSCryptResolver represents the config options for setting up a Resolver.
type DNSCryptResolver struct {
client *dnscrypt.Client
serverInfo *dnscrypt.ServerInfo
server string
resolverOptions Options
}
// DNSCryptResolverOpts holds options for setting up a DNSCrypt resolver.
type DNSCryptResolverOpts struct {
IPv4Only bool
IPv6Only bool
UseTLS bool
UseTCP bool
}
// NewDNSCryptResolver accepts a list of nameservers and configures a DNS resolver.
func NewDNSCryptResolver(server string, dnscryptOpts DNSCryptResolverOpts, resolverOpts Options) (Resolver, error) {
net := "udp"
if dnscryptOpts.UseTCP {
net = "tcp"
}
client := &dnscrypt.Client{Proto: net, AdjustPayloadSize: true}
serverInfo, _, err := client.Dial(server)
if err != nil {
return nil, err
}
return &DNSCryptResolver{
client: client,
serverInfo: serverInfo,
server: server,
resolverOptions: resolverOpts,
}, nil
}
// Lookup takes a dns.Question and sends them to DNS Server.
// It parses the Response from the server in a custom output format.
func (r *DNSCryptResolver) Lookup(question dns.Question) (Response, error) {
var (
rsp Response
messages = prepareMessages(question, r.resolverOptions.Ndots, r.resolverOptions.SearchList)
)
for _, msg := range messages {
r.resolverOptions.Logger.WithFields(logrus.Fields{
"domain": msg.Question[0].Name,
"ndots": r.resolverOptions.Ndots,
"nameserver": r.serverInfo.ProviderName,
}).Debug("Attempting to resolve")
in, rtt, err := r.client.Exchange(&msg, r.serverInfo)
if err != nil {
return rsp, err
}
// pack questions in output.
for _, q := range msg.Question {
ques := Question{
Name: q.Name,
Class: dns.ClassToString[q.Qclass],
Type: dns.TypeToString[q.Qtype],
}
rsp.Questions = append(rsp.Questions, ques)
}
// get the authorities and answers.
output := parseMessage(in, rtt, r.server)
rsp.Authorities = output.Authorities
rsp.Answers = output.Answers
if len(output.Answers) > 0 {
// stop iterating the searchlist.
break
}
}
return rsp, nil
}

View file

@ -127,6 +127,17 @@ func LoadResolvers(opts Options) ([]Resolver, error) {
}
rslvrs = append(rslvrs, rslvr)
}
if ns.Type == models.DNSCryptResolver {
opts.Logger.Debug("initiating DNSCrypt resolver")
rslvr, err := NewDNSCryptResolver(ns.Address,
DNSCryptResolverOpts{
UseTCP: false,
}, resolverOpts)
if err != nil {
return rslvrs, err
}
rslvrs = append(rslvrs, rslvr)
}
}
return rslvrs, nil
}