From dbad97ebc60e209d2f49f9fae003d056e4e6c898 Mon Sep 17 00:00:00 2001 From: LukeHandle Date: Mon, 11 Apr 2016 23:59:59 +0100 Subject: [PATCH 1/2] Retry logic for dnsQuery Added a slice of NS to be used when retrying queries. Also used with FindZoneByFqdn() Adjusted 2 error messages given to better differentiate the returned error string --- acme/dns_challenge.go | 48 ++++++++++++++-------- acme/dns_challenge_manual.go | 4 +- providers/dns/cloudflare/cloudflare.go | 2 +- providers/dns/digitalocean/digitalocean.go | 4 +- providers/dns/dnsimple/dnsimple.go | 2 +- providers/dns/gandi/gandi.go | 2 +- providers/dns/gandi/gandi_test.go | 2 +- providers/dns/rfc2136/rfc2136.go | 2 +- providers/dns/route53/route53.go | 2 +- 9 files changed, 40 insertions(+), 28 deletions(-) diff --git a/acme/dns_challenge.go b/acme/dns_challenge.go index 7b31e595..3a511d2f 100644 --- a/acme/dns_challenge.go +++ b/acme/dns_challenge.go @@ -21,7 +21,10 @@ var ( fqdnToZone = map[string]string{} ) -var RecursiveNameserver = "google-public-dns-a.google.com:53" +var RecursiveNameservers = []string{ + "google-public-dns-a.google.com:53", + "google-public-dns-b.google.com:53", +} // DNS01Record returns a DNS record which will fulfill the `dns-01` challenge func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) { @@ -56,12 +59,12 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error { err = s.provider.Present(domain, chlng.Token, keyAuth) if err != nil { - return fmt.Errorf("Error presenting token %s", err) + return fmt.Errorf("Error presenting token: %s", err) } defer func() { err := s.provider.CleanUp(domain, chlng.Token, keyAuth) if err != nil { - log.Printf("Error cleaning up %s %v ", domain, err) + log.Printf("Error cleaning up %s: %v ", domain, err) } }() @@ -90,7 +93,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error { // checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers. func checkDNSPropagation(fqdn, value string) (bool, error) { // Initial attempt to resolve at the recursive NS - r, err := dnsQuery(fqdn, dns.TypeTXT, RecursiveNameserver, true) + r, err := dnsQuery(fqdn, dns.TypeTXT, RecursiveNameservers, true) if err != nil { return false, err } @@ -117,7 +120,7 @@ func checkDNSPropagation(fqdn, value string) (bool, error) { // checkAuthoritativeNss queries each of the given nameservers for the expected TXT record. func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) { for _, ns := range nameservers { - r, err := dnsQuery(fqdn, dns.TypeTXT, net.JoinHostPort(ns, "53"), false) + r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false) if err != nil { return false, err } @@ -144,9 +147,9 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro return true, nil } -// dnsQuery sends a DNS query to the given nameserver. +// dnsQuery will query a nameserver, iterating through the supplied servers as it retries // The nameserver should include a port, to facilitate testing where we talk to a mock dns server. -func dnsQuery(fqdn string, rtype uint16, nameserver string, recursive bool) (in *dns.Msg, err error) { +func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) { m := new(dns.Msg) m.SetQuestion(fqdn, rtype) m.SetEdns0(4096, false) @@ -155,12 +158,21 @@ func dnsQuery(fqdn string, rtype uint16, nameserver string, recursive bool) (in m.RecursionDesired = false } - in, err = dns.Exchange(m, nameserver) - if err == dns.ErrTruncated { - tcp := &dns.Client{Net: "tcp"} - in, _, err = tcp.Exchange(m, nameserver) - } + // Will rety the request based on the number of servers (n+1) + for i := 1; i <= len(nameservers)+1; i++ { + ns := nameservers[i%len(nameservers)] + in, err = dns.Exchange(m, ns) + if err == dns.ErrTruncated { + tcp := &dns.Client{Net: "tcp"} + // If the TCP request suceeds, the err will reset to nil + in, _, err = tcp.Exchange(m, ns) + } + + if err == nil { + break + } + } return } @@ -168,12 +180,12 @@ func dnsQuery(fqdn string, rtype uint16, nameserver string, recursive bool) (in func lookupNameservers(fqdn string) ([]string, error) { var authoritativeNss []string - zone, err := FindZoneByFqdn(fqdn, RecursiveNameserver) + zone, err := FindZoneByFqdn(fqdn, RecursiveNameservers) if err != nil { return nil, err } - r, err := dnsQuery(zone, dns.TypeNS, RecursiveNameserver, true) + r, err := dnsQuery(zone, dns.TypeNS, RecursiveNameservers, true) if err != nil { return nil, err } @@ -191,7 +203,7 @@ func lookupNameservers(fqdn string) ([]string, error) { } // FindZoneByFqdn determines the zone of the given fqdn -func FindZoneByFqdn(fqdn, nameserver string) (string, error) { +func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) { // Do we have it cached? if zone, ok := fqdnToZone[fqdn]; ok { return zone, nil @@ -203,13 +215,13 @@ func FindZoneByFqdn(fqdn, nameserver string) (string, error) { // Name servers authoritative for a zone MUST include the SOA record of // the zone in the authority section of the response when reporting an // NXDOMAIN or indicating that no data (NODATA) of the requested type exists - in, err := dnsQuery(fqdn, dns.TypeSOA, nameserver, true) + in, err := dnsQuery(fqdn, dns.TypeSOA, nameservers, true) if err != nil { return "", err } if in.Rcode != dns.RcodeNameError { if in.Rcode != dns.RcodeSuccess { - return "", fmt.Errorf("NS %s returned %s for %s", nameserver, dns.RcodeToString[in.Rcode], fqdn) + return "", fmt.Errorf("The NS returned %s for %s", dns.RcodeToString[in.Rcode], fqdn) } // We have a success, so one of the answers has to be a SOA RR for _, ans := range in.Answer { @@ -225,7 +237,7 @@ func FindZoneByFqdn(fqdn, nameserver string) (string, error) { return checkIfTLD(fqdn, soa) } } - return "", fmt.Errorf("NS %s did not return the expected SOA record in the authority section", nameserver) + return "", fmt.Errorf("The NS did not return the expected SOA record in the authority section") } func checkIfTLD(fqdn string, soa *dns.SOA) (string, error) { diff --git a/acme/dns_challenge_manual.go b/acme/dns_challenge_manual.go index ccb79ad7..240384e6 100644 --- a/acme/dns_challenge_manual.go +++ b/acme/dns_challenge_manual.go @@ -23,7 +23,7 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error { fqdn, value, ttl := DNS01Record(domain, keyAuth) dnsRecord := fmt.Sprintf(dnsTemplate, fqdn, ttl, value) - authZone, err := FindZoneByFqdn(fqdn, RecursiveNameserver) + authZone, err := FindZoneByFqdn(fqdn, RecursiveNameservers) if err != nil { return err } @@ -42,7 +42,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error { fqdn, _, ttl := DNS01Record(domain, keyAuth) dnsRecord := fmt.Sprintf(dnsTemplate, fqdn, ttl, "...") - authZone, err := FindZoneByFqdn(fqdn, RecursiveNameserver) + authZone, err := FindZoneByFqdn(fqdn, RecursiveNameservers) if err != nil { return err } diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index db8607e4..e566224e 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -104,7 +104,7 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { Name string `json:"name"` } - authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameserver) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index 91880636..da261b39 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -63,7 +63,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { fqdn, value, _ := acme.DNS01Record(domain, keyAuth) - authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameserver) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } @@ -122,7 +122,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("unknown record ID for '%s'", fqdn) } - authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameserver) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index e9f116fb..961fdd88 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -79,7 +79,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, string, error) { return "", "", fmt.Errorf("DNSimple API call failed: %v", err) } - authZone, err := acme.FindZoneByFqdn(domain, acme.RecursiveNameserver) + authZone, err := acme.FindZoneByFqdn(domain, acme.RecursiveNameservers) if err != nil { return "", "", err } diff --git a/providers/dns/gandi/gandi.go b/providers/dns/gandi/gandi.go index ae525130..422b02a2 100644 --- a/providers/dns/gandi/gandi.go +++ b/providers/dns/gandi/gandi.go @@ -75,7 +75,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ttl = 300 // 300 is gandi minimum value for ttl } // find authZone and Gandi zone_id for fqdn - authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameserver) + authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err) } diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index a24d84d4..15919e2e 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -71,7 +71,7 @@ func TestDNSProvider(t *testing.T) { })) defer fakeServer.Close() // define function to override findZoneByFqdn with - fakeFindZoneByFqdn := func(fqdn, nameserver string) (string, error) { + fakeFindZoneByFqdn := func(fqdn string, nameserver []string) (string, error) { return "example.com.", nil } // override gandi endpoint and findZoneByFqdn function diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 9f597aa2..43a95f18 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -82,7 +82,7 @@ func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { // Find the zone for the given fqdn - zone, err := acme.FindZoneByFqdn(fqdn, r.nameserver) + zone, err := acme.FindZoneByFqdn(fqdn, []string{r.nameserver}) if err != nil { return err } diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index 8d1ff7c8..ac6c110b 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -124,7 +124,7 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { } func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { - authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameserver) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } From a684bab9a4d1ba1f46deaf81072ad919bce46349 Mon Sep 17 00:00:00 2001 From: LukeHandle Date: Tue, 12 Apr 2016 07:30:00 +0100 Subject: [PATCH 2/2] Fix typo in "retry" --- acme/dns_challenge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/dns_challenge.go b/acme/dns_challenge.go index 3a511d2f..b77e916a 100644 --- a/acme/dns_challenge.go +++ b/acme/dns_challenge.go @@ -158,7 +158,7 @@ func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) ( m.RecursionDesired = false } - // Will rety the request based on the number of servers (n+1) + // Will retry the request based on the number of servers (n+1) for i := 1; i <= len(nameservers)+1; i++ { ns := nameservers[i%len(nameservers)] in, err = dns.Exchange(m, ns)