Revamp dnscrypt-proxy -resolve

This commit is contained in:
Frank Denis 2021-01-02 22:20:52 +01:00
parent a584effbe9
commit fc82a6c05e
4 changed files with 338 additions and 49 deletions

View File

@ -6,6 +6,10 @@
* `generate-domains-blacklist.py` has been renamed to
`generate-domains-blocklist.py`, and the configuration files
have been renamed as well.
- `dnscrypt-proxy -resolve` has been completely revamped, and now requires
the configuration file to be accessible. It will send a query to an IP address
of the `dnscrypt-proxy` server by default. Sending queries to arbitrary
servers is also supported with the `-resolve name,address` syntax.
- Server lists can't be older than a week any more, even if directory
permissions are incorrect and cache files cannot be written.
- macOS/arm64 is now officially supported.

View File

@ -277,6 +277,7 @@ type CaptivePortalsConfig struct {
}
type ConfigFlags struct {
Resolve *string
List *bool
ListAll *bool
JSONOutput *bool
@ -314,6 +315,16 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
if err != nil {
return err
}
if flags.Resolve != nil && len(*flags.Resolve) > 0 {
addr := "127.0.0.1:53"
if len(config.ListenAddresses) > 0 {
addr = config.ListenAddresses[0]
}
Resolve(addr, *flags.Resolve, len(config.ServerNames) == 1)
os.Exit(0)
}
if err := cdFileDir(foundConfigFile); err != nil {
return err
}

View File

@ -41,8 +41,8 @@ func main() {
svcFlag := flag.String("service", "", fmt.Sprintf("Control the system service: %q", service.ControlAction))
version := flag.Bool("version", false, "print current proxy version")
resolve := flag.String("resolve", "", "resolve a name using system libraries")
flags := ConfigFlags{}
flags.Resolve = flag.String("resolve", "", "resolve a DNS name (string can be <name> or <name>,<resolver address>)")
flags.List = flag.Bool("list", false, "print the list of available resolvers for the enabled filters")
flags.ListAll = flag.Bool("list-all", false, "print the complete list of available resolvers, ignoring filters")
flags.JSONOutput = flag.Bool("json", false, "output list as JSON")
@ -58,10 +58,6 @@ func main() {
fmt.Println(AppVersion)
os.Exit(0)
}
if resolve != nil && len(*resolve) > 0 {
Resolve(*resolve)
os.Exit(0)
}
app := &App{
flags: &flags,

View File

@ -1,58 +1,336 @@
package main
import (
"errors"
"fmt"
"net"
"os"
"strings"
"time"
"github.com/miekg/dns"
)
const myResolverHost string = "resolver.dnscrypt.info"
const myResolverHost string = "resolver.dnscrypt.info."
const nonexistentName string = "nonexistent-zone.dnscrypt-test."
func Resolve(name string) {
fmt.Printf("Resolving [%s]\n\n", name)
fmt.Printf("Canonical name: ")
cname, err := net.LookupCNAME(name)
if err != nil {
fmt.Println("-")
} else {
fmt.Println(cname)
func resolveQuery(server string, qName string, qType uint16) (*dns.Msg, error) {
client := new(dns.Client)
client.ReadTimeout = 10 * time.Second
msg := &dns.Msg{
MsgHdr: dns.MsgHdr{
RecursionDesired: true,
Opcode: dns.OpcodeQuery,
},
Question: make([]dns.Question, 1),
}
fmt.Printf("IP addresses: ")
addrs, err := net.LookupHost(name)
if err != nil {
fmt.Println("-")
} else {
fmt.Println(strings.Join(addrs, ", "))
options := &dns.OPT{
Hdr: dns.RR_Header{
Name: ".",
Rrtype: dns.TypeOPT,
},
}
fmt.Printf("TXT records: ")
txt, err := net.LookupTXT(name)
if err != nil {
fmt.Println("-")
} else {
fmt.Println(strings.Join(txt, " "))
}
mxs, _ := net.LookupMX(name)
if len(mxs) > 0 {
fmt.Printf("Mail servers: %d mail servers found\n", len(mxs))
}
ns, _ := net.LookupNS(name)
if len(ns) > 0 {
fmt.Printf("Name servers: %d name servers found\n", len(ns))
}
resIP, err := net.LookupHost(myResolverHost)
if err == nil && len(resIP) > 0 {
fmt.Printf("Resolver IP: %s", resIP[0])
rev, err := net.LookupAddr(resIP[0])
if err == nil && len(rev) > 0 {
fmt.Printf(" (%s)", rev[0])
msg.Extra = append(msg.Extra, options)
options.SetDo()
options.SetUDPSize(uint16(MaxDNSPacketSize))
msg.Question[0] = dns.Question{Name: qName, Qtype: qType, Qclass: dns.ClassINET}
msg.Id = dns.Id()
for i := 0; i < 2; i++ {
response, rtt, err := client.Exchange(msg, server)
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
continue
}
fmt.Println("")
_ = rtt
if err != nil {
return nil, err
}
return response, nil
}
return nil, errors.New("Timeout")
}
func Resolve(server string, name string, singleResolver bool) {
parts := strings.SplitN(name, ",", 2)
if len(parts) == 2 {
name, server = parts[0], parts[1]
singleResolver = true
}
host, port := ExtractHostAndPort(server, 53)
if host == "0.0.0.0" {
host = "127.0.0.1"
} else if host == "[::]" {
host = "[::1]"
}
server = fmt.Sprintf("%s:%d", host, port)
fmt.Printf("Resolving [%s] using %s port %d\n\n", name, host, port)
name = dns.Fqdn(name)
cname := name
for {
response, err := resolveQuery(server, myResolverHost, dns.TypeA)
if err != nil {
fmt.Printf("Unable to resolve: [%s]\n", err)
os.Exit(1)
}
fmt.Printf("Resolver : ")
res := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Class != dns.ClassINET {
continue
}
var ip string
if answer.Header().Rrtype == dns.TypeA {
ip = answer.(*dns.A).A.String()
} else if answer.Header().Rrtype == dns.TypeAAAA {
ip = answer.(*dns.AAAA).AAAA.String()
}
if rev, err := dns.ReverseAddr(ip); err == nil {
response, err = resolveQuery(server, rev, dns.TypePTR)
if err != nil {
break
}
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypePTR || answer.Header().Class != dns.ClassINET {
continue
}
ip = ip + " (" + answer.(*dns.PTR).Ptr + ")"
break
}
}
res = append(res, ip)
}
if len(res) == 0 {
fmt.Println("-")
} else {
fmt.Println(strings.Join(res, ", "))
}
break
}
if singleResolver {
for {
fmt.Printf("Lying : ")
response, err := resolveQuery(server, nonexistentName, dns.TypeA)
if err != nil {
break
}
if response.Rcode == dns.RcodeSuccess {
fmt.Println("yes. That resolver returns wrong responses")
} else if response.Rcode == dns.RcodeNameError {
fmt.Println("no")
} else {
fmt.Printf("unknown - query returned %s\n", dns.RcodeToString[response.Rcode])
}
if response.Rcode == dns.RcodeNameError {
fmt.Printf("DNSSEC : ")
if response.AuthenticatedData {
fmt.Println("yes, the resolver supports DNSSEC")
} else {
fmt.Println("no, the resolver doesn't support DNSSEC")
fmt.Println(response)
}
}
break
}
} else {
fmt.Println("Multiple resolvers have been configured; this is just one one of them.")
}
fmt.Println("")
cname:
for {
fmt.Printf("Canonical name: ")
for i := 0; i < 100; i++ {
response, err := resolveQuery(server, cname, dns.TypeCNAME)
if err != nil {
break cname
}
found := false
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeCNAME || answer.Header().Class != dns.ClassINET {
continue
}
cname = answer.(*dns.CNAME).Target
found = true
break
}
if !found {
break
}
}
fmt.Println(cname)
break
}
fmt.Println("")
for {
fmt.Printf("IPv4 addresses: ")
response, err := resolveQuery(server, cname, dns.TypeA)
if err != nil {
break
}
ipv4 := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeA || answer.Header().Class != dns.ClassINET {
continue
}
ipv4 = append(ipv4, answer.(*dns.A).A.String())
}
if len(ipv4) == 0 {
fmt.Println("-")
} else {
fmt.Println(strings.Join(ipv4, ", "))
}
break
}
for {
fmt.Printf("IPv6 addresses: ")
response, err := resolveQuery(server, cname, dns.TypeAAAA)
if err != nil {
break
}
ipv6 := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeAAAA || answer.Header().Class != dns.ClassINET {
continue
}
ipv6 = append(ipv6, answer.(*dns.AAAA).AAAA.String())
}
if len(ipv6) == 0 {
fmt.Println("-")
} else {
fmt.Println(strings.Join(ipv6, ", "))
}
break
}
fmt.Println("")
for {
fmt.Printf("Name servers : ")
response, err := resolveQuery(server, cname, dns.TypeNS)
if err != nil {
break
}
nss := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeNS || answer.Header().Class != dns.ClassINET {
continue
}
nss = append(nss, answer.(*dns.NS).Ns)
}
if len(nss) == 0 {
fmt.Println("No name servers found")
} else {
fmt.Println(strings.Join(nss, ", "))
}
fmt.Printf("DNSSEC signed : ")
if response.AuthenticatedData {
fmt.Println("yes")
} else {
fmt.Println("no")
}
break
}
for {
fmt.Printf("Mail servers : ")
response, err := resolveQuery(server, cname, dns.TypeMX)
if err != nil {
break
}
mxs := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeMX || answer.Header().Class != dns.ClassINET {
continue
}
mxs = append(mxs, answer.(*dns.MX).Mx)
}
if len(mxs) == 0 {
fmt.Println("no mail servers found")
} else if len(mxs) > 1 {
fmt.Printf("%d mail servers found\n", len(mxs))
} else {
fmt.Println("1 mail servers found")
}
break
}
fmt.Println("")
for {
fmt.Printf("HTTPS alias : ")
response, err := resolveQuery(server, cname, dns.TypeHTTPS)
if err != nil {
break
}
aliases := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeHTTPS || answer.Header().Class != dns.ClassINET {
continue
}
https := answer.(*dns.HTTPS)
if https.Priority != 0 || len(https.Target) < 2 {
continue
}
aliases = append(aliases, https.Target)
}
if len(aliases) == 0 {
fmt.Println("-")
} else {
fmt.Println(strings.Join(aliases, ", "))
}
fmt.Printf("HTTPS info : ")
info := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeHTTPS || answer.Header().Class != dns.ClassINET {
continue
}
https := answer.(*dns.HTTPS)
if https.Priority == 0 || len(https.Target) > 1 {
continue
}
for _, value := range https.Value {
info = append(info, fmt.Sprintf("[%s]=[%s]", value.Key(), value.String()))
}
}
if len(info) == 0 {
fmt.Println("-")
} else {
fmt.Println(strings.Join(info, ", "))
}
break
}
fmt.Println("")
for {
fmt.Printf("TXT records : ")
response, err := resolveQuery(server, cname, dns.TypeTXT)
if err != nil {
break
}
txt := make([]string, 0)
for _, answer := range response.Answer {
if answer.Header().Rrtype != dns.TypeTXT || answer.Header().Class != dns.ClassINET {
continue
}
txt = append(txt, strings.Join(answer.(*dns.TXT).Txt, " "))
}
if len(txt) == 0 {
fmt.Println("-")
} else {
fmt.Println(strings.Join(txt, ", "))
}
break
}
fmt.Println("")
}