From 6336dbbff74b937d6d72bb725094560637bba1c0 Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sun, 23 Oct 2022 23:32:47 +0200 Subject: [PATCH 1/5] add dns query handling changes --- management/dns_update.py | 26 ++++++++++++++++++++------ management/status_checks.py | 9 +++++++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 1567371ab..d7c1f6179 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -983,6 +983,7 @@ def set_custom_dns_record(qname, rtype, value, action, env): def get_secondary_dns(custom_dns, mode=None): resolver = dns.resolver.get_default_resolver() resolver.timeout = 10 + resolver.lifetime = 10 values = [] for qname, rtype, value in custom_dns: @@ -1000,10 +1001,17 @@ def get_secondary_dns(custom_dns, mode=None): # doesn't. if not hostname.startswith("xfr:"): if mode == "xfr": - response = dns.resolver.resolve(hostname+'.', "A", raise_on_no_answer=False) - values.extend(map(str, response)) - response = dns.resolver.resolve(hostname+'.', "AAAA", raise_on_no_answer=False) - values.extend(map(str, response)) + try: + response = resolver.resolve(hostname+'.', "A", raise_on_no_answer=False) + values.extend(map(str, response)) + except dns.exception.DNSException: + pass + + try: + response = resolver.resolve(hostname+'.', "AAAA", raise_on_no_answer=False) + values.extend(map(str, response)) + except dns.exception.DNSException: + pass continue values.append(hostname) @@ -1021,6 +1029,8 @@ def set_secondary_dns(hostnames, env): # Validate that all hostnames are valid and that all zone-xfer IP addresses are valid. resolver = dns.resolver.get_default_resolver() resolver.timeout = 5 + resolver.lifetime = 5 + for item in hostnames: if not item.startswith("xfr:"): # Resolve hostname. @@ -1030,7 +1040,11 @@ def set_secondary_dns(hostnames, env): try: response = resolver.resolve(item, "AAAA") except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - raise ValueError("Could not resolve the IP address of %s." % item) + pass + except (dns.resolver.Timeout): + raise ValueError("Could not resolve the IP address of %s due to timeout." % item) + except (dns.resolver.Timeout): + raise ValueError("Could not resolve the IP address of %s due to timeout." % item) else: # Validate IP address. try: @@ -1062,7 +1076,7 @@ def get_custom_dns_records(custom_dns, qname, rtype): def build_recommended_dns(env): ret = [] for (domain, zonefile, records) in build_zones(env): - # remove records that we don't dislay + # remove records that we don't display records = [r for r in records if r[3] is not False] # put Required at the top, then Recommended, then everythiing else diff --git a/management/status_checks.py b/management/status_checks.py index 0d5554410..675b8f741 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -541,7 +541,7 @@ def check_dns_zone(domain, env, output, dns_zonefiles): for ns in custom_secondary_ns: # We must first resolve the nameserver to an IP address so we can query it. ns_ips = query_dns(ns, "A") - if not ns_ips: + if not ns_ips or ns_ips in {'[Not Set]', '[timeout]'}: output.print_error("Secondary nameserver %s is not valid (it doesn't resolve to an IP address)." % ns) continue # Choose the first IP if nameserver returns multiple @@ -788,12 +788,17 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None, as_list=False): # running bind server), or if the 'at' argument is specified, use that host # as the nameserver. resolver = dns.resolver.get_default_resolver() - if at: + + # Make sure at is not a string that cannot be used as a nameserver + if at and at not in {'[Not set]', '[timeout]'}: resolver = dns.resolver.Resolver() resolver.nameservers = [at] # Set a timeout so that a non-responsive server doesn't hold us back. resolver.timeout = 5 + # The number of seconds to spend trying to get an answer to the question. If the + # lifetime expires a dns.exception.Timeout exception will be raised. + resolver.lifetime = 5 # Do the query. try: From c462008a5e8067b5f71ddc4c542b88b9205425f3 Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sun, 23 Oct 2022 23:35:37 +0200 Subject: [PATCH 2/5] replace exception pass with error message --- management/dns_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/dns_update.py b/management/dns_update.py index d7c1f6179..3c761ac83 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -1040,7 +1040,7 @@ def set_secondary_dns(hostnames, env): try: response = resolver.resolve(item, "AAAA") except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - pass + raise ValueError("Could not resolve the IP address of %s due to dns error." % item) except (dns.resolver.Timeout): raise ValueError("Could not resolve the IP address of %s due to timeout." % item) except (dns.resolver.Timeout): From 8d8d6e158b18a5653a4d3cd062812626f77b0940 Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sun, 23 Oct 2022 23:40:04 +0200 Subject: [PATCH 3/5] simplify dns exception catching --- management/dns_update.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 3c761ac83..9256c0ff6 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -1036,15 +1036,11 @@ def set_secondary_dns(hostnames, env): # Resolve hostname. try: response = resolver.resolve(item, "A") - except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout): try: response = resolver.resolve(item, "AAAA") - except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - raise ValueError("Could not resolve the IP address of %s due to dns error." % item) - except (dns.resolver.Timeout): - raise ValueError("Could not resolve the IP address of %s due to timeout." % item) - except (dns.resolver.Timeout): - raise ValueError("Could not resolve the IP address of %s due to timeout." % item) + except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout): + raise ValueError("Could not resolve the IP address of %s." % item) else: # Validate IP address. try: From ecbb2100581ec75b506036b1f4e47cdfecc49e94 Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Wed, 26 Oct 2022 23:14:57 +0200 Subject: [PATCH 4/5] Add dns time out handling changes --- management/dns_update.py | 26 +++++++++++++++++++------- management/status_checks.py | 32 +++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 9256c0ff6..ead03a55b 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -1030,17 +1030,29 @@ def set_secondary_dns(hostnames, env): resolver = dns.resolver.get_default_resolver() resolver.timeout = 5 resolver.lifetime = 5 - + for item in hostnames: if not item.startswith("xfr:"): # Resolve hostname. - try: - response = resolver.resolve(item, "A") - except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout): + tries = 2 + + while tries > 0: + tries = tries - 1 try: - response = resolver.resolve(item, "AAAA") - except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout): - raise ValueError("Could not resolve the IP address of %s." % item) + response = resolver.resolve(item, "A") + tries = 0 + except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + try: + response = resolver.resolve(item, "AAAA") + tries = 0 + except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + raise ValueError("Could not resolve the IP address of %s." % item) + except (dns.resolver.Timeout): + if tries < 1: + raise ValueError("Could not resolve the IP address of %s due to timeout." % item) + except (dns.resolver.Timeout): + if tries < 1: + raise ValueError("Could not resolve the IP address of %s due to timeout." % item) else: # Validate IP address. try: diff --git a/management/status_checks.py b/management/status_checks.py index 675b8f741..29ce2dc1c 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -303,7 +303,7 @@ def run_network_checks(env, output): # by a spammer, or the user may be deploying on a residential network. We # will not be able to reliably send mail in these cases. rev_ip4 = ".".join(reversed(env['PUBLIC_IP'].split('.'))) - zen = query_dns(rev_ip4+'.zen.spamhaus.org', 'A', nxdomain=None) + zen = query_dns(rev_ip4+'.zen.spamhaus.org', 'A', nxdomain=None, retry = False) if zen is None: output.print_ok("IP address is not blacklisted by zen.spamhaus.org.") elif zen == "[timeout]": @@ -739,7 +739,7 @@ def check_mail_domain(domain, env, output): # Stop if the domain is listed in the Spamhaus Domain Block List. # The user might have chosen a domain that was previously in use by a spammer # and will not be able to reliably send mail. - dbl = query_dns(domain+'.dbl.spamhaus.org', "A", nxdomain=None) + dbl = query_dns(domain+'.dbl.spamhaus.org', "A", nxdomain=None, retry=False) if dbl is None: output.print_ok("Domain is not blacklisted by dbl.spamhaus.org.") elif dbl == "[timeout]": @@ -775,7 +775,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output): # website for also needs a signed certificate. check_ssl_cert(domain, rounded_time, ssl_certificates, env, output) -def query_dns(qname, rtype, nxdomain='[Not Set]', at=None, as_list=False): +def query_dns(qname, rtype, nxdomain='[Not Set]', at=None, as_list=False, retry=True): # Make the qname absolute by appending a period. Without this, dns.resolver.query # will fall back a failed lookup to a second query with this machine's hostname # appended. This has been causing some false-positive Spamhaus reports. The @@ -800,15 +800,25 @@ def query_dns(qname, rtype, nxdomain='[Not Set]', at=None, as_list=False): # lifetime expires a dns.exception.Timeout exception will be raised. resolver.lifetime = 5 + if retry: + tries = 2 + else: + tries = 1 + # Do the query. - try: - response = resolver.resolve(qname, rtype) - except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - # Host did not have an answer for this query; not sure what the - # difference is between the two exceptions. - return nxdomain - except dns.exception.Timeout: - return "[timeout]" + while tries > 0: + tries = tries - 1 + try: + response = resolver.resolve(qname, rtype, search=True) + tries = 0 + except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + # Host did not have an answer for this query; not sure what the + # difference is between the two exceptions. + if tries < 1: + return nxdomain + except dns.exception.Timeout: + if tries < 1: + return "[timeout]" # Normalize IP addresses. IP address --- especially IPv6 addresses --- can # be expressed in equivalent string forms. Canonicalize the form before From c983037ff01676bcae17b2b3cca0fa3b9a3f708b Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Thu, 3 Nov 2022 08:34:32 +0100 Subject: [PATCH 5/5] Add not set case to blacklist lookup result handling --- management/status_checks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/management/status_checks.py b/management/status_checks.py index 29ce2dc1c..f7ab680cc 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -308,6 +308,8 @@ def run_network_checks(env, output): output.print_ok("IP address is not blacklisted by zen.spamhaus.org.") elif zen == "[timeout]": output.print_warning("Connection to zen.spamhaus.org timed out. We could not determine whether your server's IP address is blacklisted. Please try again later.") + elif zen == "[Not Set]": + output.print_warning("Could not connect to zen.spamhaus.org. We could not determine whether your server's IP address is blacklisted. Please try again later.") else: output.print_error("""The IP address of this machine %s is listed in the Spamhaus Block List (code %s), which may prevent recipients from receiving your email. See http://www.spamhaus.org/query/ip/%s.""" @@ -744,6 +746,8 @@ def check_mail_domain(domain, env, output): output.print_ok("Domain is not blacklisted by dbl.spamhaus.org.") elif dbl == "[timeout]": output.print_warning("Connection to dbl.spamhaus.org timed out. We could not determine whether the domain {} is blacklisted. Please try again later.".format(domain)) + elif dbl == "[Not Set]": + output.print_warning("Could not connect to dbl.spamhaus.org. We could not determine whether the domain {} is blacklisted. Please try again later.".format(domain)) else: output.print_error("""This domain is listed in the Spamhaus Domain Block List (code %s), which may prevent recipients from receiving your mail.