Skip to content

Commit 6298bc4

Browse files
authored
Retry DNS queries if they time out (#193)
* Start work on DNS timeout retries * Add timeout retries to SPF * Add timeout_retries to mta_sts * Add --timeout-retries to the CLI * Add --timeout -retries to the CLI --------- Co-authored-by: Sean Whalen <seanthegeek@users.noreply.github.com>
1 parent 12c93ee commit 6298bc4

File tree

10 files changed

+269
-26
lines changed

10 files changed

+269
-26
lines changed

checkdmarc/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def check_domains(
5959
nameservers: list[str] = None,
6060
resolver: dns.resolver.Resolver = None,
6161
timeout: float = 2.0,
62+
timeout_retries: int = 2,
6263
wait: float = 0.0,
6364
) -> Union[OrderedDict, list[OrderedDict]]:
6465
"""
@@ -79,6 +80,7 @@ def check_domains(
7980
resolver (dns.resolver.Resolver): A resolver object to use for DNS
8081
requests
8182
timeout (float): number of seconds to wait for an answer from DNS
83+
timeout_retries (int): The number of times to reattempt a query after a timeout
8284
wait (float): number of seconds to wait between processing domains
8385
8486
Returns:
@@ -139,11 +141,16 @@ def check_domains(
139141
nameservers=nameservers,
140142
resolver=resolver,
141143
timeout=timeout,
144+
timeout_retries=timeout_retries,
142145
)
143146

144147
mta_sts_mx_patterns = None
145148
domain_results["mta_sts"] = check_mta_sts(
146-
domain, nameservers=nameservers, resolver=resolver, timeout=timeout
149+
domain,
150+
nameservers=nameservers,
151+
resolver=resolver,
152+
timeout=timeout,
153+
timeout_retries=timeout_retries,
147154
)
148155
if domain_results["mta_sts"]["valid"]:
149156
mta_sts_mx_patterns = domain_results["mta_sts"]["policy"]["mx"]
@@ -155,6 +162,7 @@ def check_domains(
155162
nameservers=nameservers,
156163
resolver=resolver,
157164
timeout=timeout,
165+
timeout_retries=timeout_retries,
158166
)
159167

160168
domain_results["spf"] = check_spf(
@@ -163,6 +171,7 @@ def check_domains(
163171
nameservers=nameservers,
164172
resolver=resolver,
165173
timeout=timeout,
174+
timeout_retries=timeout_retries,
166175
)
167176

168177
domain_results["dmarc"] = check_dmarc(
@@ -172,10 +181,15 @@ def check_domains(
172181
nameservers=nameservers,
173182
resolver=resolver,
174183
timeout=timeout,
184+
timeout_retries=timeout_retries,
175185
)
176186

177187
domain_results["smtp_tls_reporting"] = check_smtp_tls_reporting(
178-
domain, nameservers=nameservers, resolver=resolver, timeout=timeout
188+
domain,
189+
nameservers=nameservers,
190+
resolver=resolver,
191+
timeout=timeout,
192+
timeout_retries=timeout_retries,
179193
)
180194
if bimi_selector is not None:
181195
domain_results["bimi"] = check_bimi(
@@ -186,6 +200,7 @@ def check_domains(
186200
nameservers=nameservers,
187201
resolver=resolver,
188202
timeout=timeout,
203+
timeout_retries=timeout_retries,
189204
)
190205

191206
results.append(domain_results)
@@ -205,6 +220,7 @@ def check_ns(
205220
nameservers: list[str] = None,
206221
resolver: dns.resolver.Resolver = None,
207222
timeout: float = 2.0,
223+
timeout_retries: int = 2,
208224
) -> OrderedDict:
209225
"""
210226
Returns a dictionary of nameservers and warnings or a dictionary with an
@@ -236,6 +252,7 @@ def check_ns(
236252
nameservers=nameservers,
237253
resolver=resolver,
238254
timeout=timeout,
255+
timeout_retries=timeout_retries,
239256
)
240257
except DNSException as error:
241258
ns_results = OrderedDict([("hostnames", []), ("error", error.__str__())])

checkdmarc/_cli.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ def _main():
8080
type=float,
8181
default=2.0,
8282
)
83+
arg_parser.add_argument(
84+
"--timeout-retries",
85+
help="number of times to reattempt a query after a timeout (default 2)",
86+
type=int,
87+
default=2,
88+
)
89+
8390
arg_parser.add_argument(
8491
"-b", "--bimi-selector", default="default", help="the BIMI selector to use"
8592
)
@@ -137,6 +144,7 @@ def _main():
137144
include_tag_descriptions=args.descriptions,
138145
nameservers=args.nameserver,
139146
timeout=args.timeout,
147+
timeout_retries=args.timeout_retries,
140148
bimi_selector=args.bimi_selector,
141149
wait=args.wait,
142150
)

checkdmarc/bimi.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ def _query_bimi_record(
650650
nameservers: list[str] = None,
651651
resolver: dns.resolver.Resolver = None,
652652
timeout: float = 2.0,
653+
timeout_retries: int = 2,
653654
):
654655
"""
655656
Queries DNS for a BIMI record
@@ -661,6 +662,7 @@ def _query_bimi_record(
661662
resolver (dns.resolver.Resolver): A resolver object to use for DNS
662663
requests
663664
timeout (float): number of seconds to wait for a record from DNS
665+
timeout_retries (int): The number of times to reattempt a query after a timeout
664666
665667
Returns:
666668
str: A record string or None
@@ -674,7 +676,12 @@ def _query_bimi_record(
674676

675677
try:
676678
records = query_dns(
677-
target, "TXT", nameservers=nameservers, resolver=resolver, timeout=timeout
679+
target,
680+
"TXT",
681+
nameservers=nameservers,
682+
resolver=resolver,
683+
timeout=timeout,
684+
timeout_retries=timeout_retries,
678685
)
679686
for record in records:
680687
if record.startswith(txt_prefix):
@@ -730,6 +737,7 @@ def query_bimi_record(
730737
nameservers: list[str] = None,
731738
resolver: dns.resolver.Resolver = None,
732739
timeout: float = 2.0,
740+
timeout_retries: int = 2,
733741
) -> OrderedDict:
734742
"""
735743
Queries DNS for a BIMI record
@@ -741,6 +749,7 @@ def query_bimi_record(
741749
resolver (dns.resolver.Resolver): A resolver object to use for DNS
742750
requests
743751
timeout (float): number of seconds to wait for a record from DNS
752+
timeout_retries (int): The number of times to reattempt a query after a timeout
744753
745754
Returns:
746755
OrderedDict: An ``OrderedDict`` with the following keys:
@@ -765,10 +774,16 @@ def query_bimi_record(
765774
nameservers=nameservers,
766775
resolver=resolver,
767776
timeout=timeout,
777+
timeout_retries=timeout_retries,
768778
)
769779
try:
770780
root_records = query_dns(
771-
domain, "TXT", nameservers=nameservers, resolver=resolver, timeout=timeout
781+
domain,
782+
"TXT",
783+
nameservers=nameservers,
784+
resolver=resolver,
785+
timeout=timeout,
786+
timeout_retries=timeout_retries,
772787
)
773788
for root_record in root_records:
774789
if root_record.startswith("v=BIMI1"):
@@ -780,7 +795,11 @@ def query_bimi_record(
780795

781796
if record is None and domain != base_domain:
782797
record = _query_bimi_record(
783-
base_domain, nameservers=nameservers, resolver=resolver, timeout=timeout
798+
base_domain,
799+
nameservers=nameservers,
800+
resolver=resolver,
801+
timeout=timeout,
802+
timeout_retries=timeout_retries,
784803
)
785804
location = base_domain
786805
if record is None:
@@ -993,6 +1012,7 @@ def check_bimi(
9931012
nameservers: list[str] = None,
9941013
resolver: dns.resolver.Resolver = None,
9951014
timeout: float = 2.0,
1015+
timeout_retries: int = 2,
9961016
) -> OrderedDict:
9971017
"""
9981018
Returns a dictionary with a parsed BIMI record or an error.
@@ -1012,6 +1032,7 @@ def check_bimi(
10121032
resolver (dns.resolver.Resolver): A resolver object to use for DNS
10131033
requests
10141034
timeout (float): number of seconds to wait for an answer from DNS
1035+
timeout_retries (int): The number of times to reattempt a query after a timeout
10151036
10161037
Returns:
10171038
OrderedDict: An ``OrderedDict`` with the following keys:

0 commit comments

Comments
 (0)