From 8a3471ad1c2f6b12a76dfb5acc3a95f70efb7bff Mon Sep 17 00:00:00 2001 From: Timothy Date: Sun, 3 May 2020 21:32:06 +0800 Subject: [PATCH] add DNS resolver, update all the handlers --- dns_resolver/dns_resolver.go | 97 ++++++++++++++++++++++++++ dns_resolver/dns_resolver_test.go | 45 ++++++++++++ go.mod | 2 +- handler/alidns/alidns_handler.go | 2 +- handler/dnspod/dnspod_handler.go | 2 +- handler/dreamhost/dreamhost_handler.go | 2 +- handler/duck/duck_handler.go | 2 +- handler/google/google_handler.go | 2 +- handler/he/he_handler.go | 2 +- utils.go | 18 +++-- 10 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 dns_resolver/dns_resolver.go create mode 100644 dns_resolver/dns_resolver_test.go diff --git a/dns_resolver/dns_resolver.go b/dns_resolver/dns_resolver.go new file mode 100644 index 0000000..2258a31 --- /dev/null +++ b/dns_resolver/dns_resolver.go @@ -0,0 +1,97 @@ +// Package dns_resolver is a simple dns resolver +// based on miekg/dns +package dns_resolver + +import ( + "errors" + "math/rand" + "net" + "os" + "strings" + "time" + + "github.com/miekg/dns" +) + +// DnsResolver represents a dns resolver +type DnsResolver struct { + Servers []string + RetryTimes int + r *rand.Rand +} + +// New initializes DnsResolver. +func New(servers []string) *DnsResolver { + for i := range servers { + servers[i] = net.JoinHostPort(servers[i], "53") + } + + return &DnsResolver{servers, len(servers) * 2, rand.New(rand.NewSource(time.Now().UnixNano()))} +} + +// NewFromResolvConf initializes DnsResolver from resolv.conf like file. +func NewFromResolvConf(path string) (*DnsResolver, error) { + if _, err := os.Stat(path); os.IsNotExist(err) { + return &DnsResolver{}, errors.New("no such file or directory: " + path) + } + config, err := dns.ClientConfigFromFile(path) + servers := []string{} + for _, ipAddress := range config.Servers { + servers = append(servers, net.JoinHostPort(ipAddress, "53")) + } + return &DnsResolver{servers, len(servers) * 2, rand.New(rand.NewSource(time.Now().UnixNano()))}, err +} + +// LookupHost returns IP addresses of provied host. +// In case of timeout retries query RetryTimes times. +func (r *DnsResolver) LookupHost(host string, dnsType uint16) ([]net.IP, error) { + return r.lookupHost(host, dnsType, r.RetryTimes) +} + +func (r *DnsResolver) lookupHost(host string, dnsType uint16, triesLeft int) ([]net.IP, error) { + m1 := new(dns.Msg) + m1.Id = dns.Id() + m1.RecursionDesired = true + m1.Question = make([]dns.Question, 1) + + switch dnsType { + case dns.TypeA: + m1.Question[0] = dns.Question{dns.Fqdn(host), dns.TypeA, dns.ClassINET} + case dns.TypeAAAA: + m1.Question[0] = dns.Question{dns.Fqdn(host), dns.TypeAAAA, dns.ClassINET} + } + + in, err := dns.Exchange(m1, r.Servers[r.r.Intn(len(r.Servers))]) + + result := []net.IP{} + + if err != nil { + if strings.HasSuffix(err.Error(), "i/o timeout") && triesLeft > 0 { + triesLeft-- + return r.lookupHost(host, dnsType, triesLeft) + } + return result, err + } + + if in != nil && in.Rcode != dns.RcodeSuccess { + return result, errors.New(dns.RcodeToString[in.Rcode]) + } + + if dnsType == dns.TypeA { + for _, record := range in.Answer { + if t, ok := record.(*dns.A); ok { + result = append(result, t.A) + } + } + } + + if dnsType == dns.TypeAAAA { + for _, record := range in.Answer { + if t, ok := record.(*dns.AAAA); ok { + result = append(result, t.AAAA) + } + } + } + + return result, err +} diff --git a/dns_resolver/dns_resolver_test.go b/dns_resolver/dns_resolver_test.go new file mode 100644 index 0000000..b87919f --- /dev/null +++ b/dns_resolver/dns_resolver_test.go @@ -0,0 +1,45 @@ +package dns_resolver + +import ( + "fmt" + "reflect" + "testing" + + "github.com/miekg/dns" +) + +func TestNew(t *testing.T) { + servers := []string{"8.8.8.8", "8.8.4.4"} + expectedServers := []string{"8.8.8.8:53", "8.8.4.4:53"} + resolver := New(servers) + + if !reflect.DeepEqual(resolver.Servers, expectedServers) { + t.Error("resolver.Servers: ", resolver.Servers, "should be equal to", expectedServers) + } +} + +func TestLookupHost_ValidServer(t *testing.T) { + resolver := New([]string{"8.8.8.8", "8.8.4.4"}) + result, err := resolver.LookupHost("google-public-dns-a.google.com", dns.TypeA) + if err != nil { + fmt.Println(err.Error()) + t.Error("Should succeed dns lookup") + } + + if result[0].String() != "8.8.8.8" { + t.Error("google-public-dns-a.google.com should be resolved to 8.8.8.8") + } +} + +func TestLookupHostIPv6_ValidServer(t *testing.T) { + resolver := New([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}) + result, err := resolver.LookupHost("google-public-dns-a.google.com", dns.TypeAAAA) + if err != nil { + fmt.Println(err.Error()) + t.Error("Should succeed dns lookup") + } + + if result[0].String() != "2001:4860:4860::8888" { + t.Error("result shoudl be: 2001:4860:4860::8888") + } +} diff --git a/go.mod b/go.mod index 2184793..301c7e1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect - github.com/miekg/dns v1.1.29 // indirect + github.com/miekg/dns v1.1.29 golang.org/x/net v0.0.0-20190923162816-aa69164e4478 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df diff --git a/handler/alidns/alidns_handler.go b/handler/alidns/alidns_handler.go index 5376d51..8e6bccd 100644 --- a/handler/alidns/alidns_handler.go +++ b/handler/alidns/alidns_handler.go @@ -40,7 +40,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. log.Println("currentIP is:", currentIP) for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/dnspod/dnspod_handler.go b/handler/dnspod/dnspod_handler.go index 901f573..87a65f6 100644 --- a/handler/dnspod/dnspod_handler.go +++ b/handler/dnspod/dnspod_handler.go @@ -54,7 +54,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/dreamhost/dreamhost_handler.go b/handler/dreamhost/dreamhost_handler.go index 2cd400f..57a4e77 100644 --- a/handler/dreamhost/dreamhost_handler.go +++ b/handler/dreamhost/dreamhost_handler.go @@ -49,7 +49,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/duck/duck_handler.go b/handler/duck/duck_handler.go index 16a1daf..7c12c15 100644 --- a/handler/duck/duck_handler.go +++ b/handler/duck/duck_handler.go @@ -57,7 +57,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/google/google_handler.go b/handler/google/google_handler.go index 0363a62..cb27145 100644 --- a/handler/google/google_handler.go +++ b/handler/google/google_handler.go @@ -45,7 +45,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. log.Println("currentIP is:", currentIP) for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/handler/he/he_handler.go b/handler/he/he_handler.go index 63d3fad..4a00f6a 100644 --- a/handler/he/he_handler.go +++ b/handler/he/he_handler.go @@ -50,7 +50,7 @@ func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns. for _, subDomain := range domain.SubDomains { hostname := subDomain + "." + domain.DomainName - lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver, handler.Configuration.IPType) //check against currently known IP, if no change, skip update if currentIP == lastIP { log.Printf("IP is the same as cached one. Skip update.\n") diff --git a/utils.go b/utils.go index 19b1fb8..53d145f 100644 --- a/utils.go +++ b/utils.go @@ -12,7 +12,8 @@ import ( "net/http" "strings" - "github.com/bogdanovich/dns_resolver" + "github.com/TimothyYe/godns/dns_resolver" + "github.com/miekg/dns" "golang.org/x/net/proxy" gomail "gopkg.in/gomail.v2" ) @@ -103,8 +104,9 @@ func GetIPFromInterface(configuration *Settings) (string, error) { } } - return ip.String(), nil - + if ip.String() != "" { + return ip.String(), nil + } } return "", errors.New("can't get a vaild address from " + configuration.IPInterface) } @@ -356,7 +358,13 @@ func buildTemplate(currentIP, domain string, tplsrc string) string { } // ResolveDNS will query DNS for a given hostname. -func ResolveDNS(hostname, resolver string) string { +func ResolveDNS(hostname, resolver, ipType string) string { + var dnsType uint16 + if ipType == "" || strings.ToUpper(ipType) == IPV4 { + dnsType = dns.TypeA + } else { + dnsType = dns.TypeAAAA + } // If no DNS server is set in config file, falls back to default resolver. if resolver == "" { @@ -373,7 +381,7 @@ func ResolveDNS(hostname, resolver string) string { // In case of i/o timeout res.RetryTimes = 5 - ip, err := res.LookupHost(hostname) + ip, err := res.LookupHost(hostname, dnsType) if err != nil { if strings.HasSuffix(err.Error(), "NXDOMAIN") { return ""