如何使用 Python 的 dns 解析器捕获 SERVFAIL 异常?
How can I catch a SERVFAIL exception using Python's dns resolver?
我想查询这样的域:
dns.resolver.resolve("dnssec-failed.org","A")
其中 return 是这样的错误:
raise NoNameservers(request=self.request, errors=self.errors)
dns.resolver.NoNameservers: All nameservers failed to answer the query dnssec-failed.org. IN A: Server 127.0.0.1 UDP port 53 answered SERVFAIL
我希望能够像这样在我的函数中捕获该异常:
def get_a_record(url):
try:
answers = dns.resolver.resolve(url,"A")
except dns.resolver.SERVFAIL:
print("SERVFAIL error for %s" % url)
except dns.resolver.NXDOMAIN:
print("No such domain %s" % url)
except dns.resolver.Timeout:
print("Timed out while resolving %s" % url)
except dns.exception.DNSException:
print("Unhandled exception")
现在我知道在上面的代码片段中 dns 解析器没有 SERVAIL 异常,但我想做的是捕获错误,能够记录它,然后继续我的脚本。是否有使用 dns 解析程序包执行此操作的正确方法,或者我是否需要调用 dig
命令并解析该结果?
编辑
为了澄清,我只使用 dnssec-failed.org 作为示例,因为它导致(我认为的)与我专门寻找的东西相同,但实际上没有任何的活跃例子。那个“东西”是指向不再使用的 IP 地址的域。换句话说,悬挂 NS 记录。
例如,我使用 AWS 借给我的 IP 地址用于某些基于 XYZ 云的应用程序,并在我的 DNS 区域中创建名称到地址的映射记录。如果我决定弃用此服务并且 return 将 ip 返回到云提供商的 ips 池但忘记从区域中删除 DNS 记录,它就会“悬空”。
这就是我要找的东西,我错误地认为 SERVFAIL 是我从 dig domain-with-no-ip.com
这样的查询中得到的响应类型
对造成的混乱表示歉意。
编辑 2
我通过使用我已经注册的域来测试它。为它配置了一个 A 记录,并将它指向一个 Ubuntu EC2 侦听端口 7272 (python3 -m http.server 7272)。等待 5 分钟让区域传播,然后我能够到达我的域,publicly。一切都很好。
然后我停止了实例,稍等片刻,然后重新启动它。回来后它有一个新的 public ip。伟大的。所以此时有一条悬空的A记录供我测试。
所以我在域上进行了挖掘和 nslookup。两者都得到了完美的答案。他们只是简单地指向现在的 old/original public ip。这是有道理的,因为 DNS 记录没有改变。唯一可以观察到的真正改变的是像 curl 这样的东西,它会超时。
因此,除非我的理解仍然错误,否则确实没有一种非常可靠的方法来查找悬空 A 记录,因为基于 n http 超时的逻辑并不一定意味着悬空记录。服务器可能只是 off/down 并且 ip 仍然附加到资源。我的理解是正确的还是我还遗漏了什么?
编辑 3
接受答案是因为即使我的问题稍微演变成其他问题,答案在技术上确实解决了我 post 的原始问题,我认为有理由接受它。
首先,dnssec-failed.org
有域名服务器,但在设计上无法通过 DNSSEC。
因此,对任何执行 DNSSEC 验证的递归名称服务器的简单查询将如预期的那样失败并返回 SERVFAIL
:
$ dig dnssec-failed.org NS +short
(no output)
$ dig dnssec-failed.org NS | grep status: | tail -1
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 46517
但是一旦您禁用 DNSSEC 验证,您就会获得应有的名称服务器:
$ dig dnssec-failed.org NS +cdflag +noall +ans +nottlunits
dnssec-failed.org. 7200 IN NS dns105.comcast.net.
dnssec-failed.org. 7200 IN NS dns101.comcast.net.
dnssec-failed.org. 7200 IN NS dns102.comcast.net.
dnssec-failed.org. 7200 IN NS dns103.comcast.net.
dnssec-failed.org. 7200 IN NS dns104.comcast.net.
现在回到Python部分。
来自 dnspython 的 resolve()
是一个高级 API 调用,它执行解析器所做的一切,即它可能从根递归到能够给你一个答案。因此,这个简单的调用可能隐藏了多个问题和响应,因此可能不会让您暴露于真正的潜在问题,但也会使用异常在输出中提供高级 API。
如您在自己的示例中所见,错误消息中的 SERVFAIL
是正确的,但这是一个 NoNameservers
异常,因为代码向注册表名称服务器询问名称服务器列表(有效,父名称服务器中有一个 DS),然后向这些名称服务器中的任何一个请求更多数据,然后它们在 DNSSEC 验证中失败,因此出现最终异常。
我不清楚你对 DNSSEC 错误的立场是什么,如果你不关心它们,或者你是否真的想研究它们并做一些特别的事情。因此,可能需要调整上述解决方案。如果您不在乎,只需记录 NoNameservers
异常并继续,一切都会正常运行,DNSSEC 验证错误将完全像损坏的域一样发生,这是每个设计。
因此,您真的需要以不同于任何其他错误的任何方式处理 DNSSEC 错误吗?为什么你不能捕获 NoNameservers
异常,记录它,然后更进一步?
否则快速(和肮脏的方式),只需解析附加到 NoNameservers
异常的错误消息,如果你看到 SERVFAIL
你可以假设(但不是 100% 确定)它是一个 DNSSEC 问题,至少根据需要走得更远。
如果您确实需要更多详细信息并确定这是一个 DNSSEC 问题,您需要执行与上述 dig
相同的操作,即执行 2 个查询,只是 CD
DNS标志,并比较结果。这意味着要“低于”resolve()
API 并直接使用 dns.query
,例如这样:
>>> import dns, dns.rcode
>>> resolver_ip = '8.8.8.8' # Use any recursive **validating** nameserver that you trust
>>> query=dns.message.make_query('dnssec-failed.org', 'A')
>>> response = dns.query.udp_with_fallback(query, resolver_ip)[0]
>>> response.rcode() == dns.rcode.SERVFAIL
True
# Now checking if disabling DNSSEC resolves the problem and gets us a reply
# If so, it really means there is a DNSSEC problem
>>> print(str(query))
id 65008
opcode QUERY
rcode NOERROR
flags RD
;QUESTION
dnssec-failed.org. IN A
;ANSWER
;AUTHORITY
;ADDITIONAL
>>> query.flags
<Flag.RD: 256>
>>> query.flags = query.flags | dns.flags.CD
>>> query.flags
<Flag.RD|CD: 272>
>>> print(str(query))
id 65008
opcode QUERY
rcode NOERROR
flags RD CD
;QUESTION
dnssec-failed.org. IN A
;ANSWER
;AUTHORITY
;ADDITIONAL
# We enabled flag "CD" aka checking disabled aka please do not do any DNSSEC validation, and now doing the same query as above again:
>>> response = dns.query.udp_with_fallback(query, resolver_ip)[0]
>>> response.rcode() == dns.rcode.SERVFAIL
False
>>> response.rcode() == dns.rcode.NOERROR
True
>>> response.answer[0][0]
<DNS IN A rdata: 69.252.80.75>
我想查询这样的域:
dns.resolver.resolve("dnssec-failed.org","A")
其中 return 是这样的错误:
raise NoNameservers(request=self.request, errors=self.errors)
dns.resolver.NoNameservers: All nameservers failed to answer the query dnssec-failed.org. IN A: Server 127.0.0.1 UDP port 53 answered SERVFAIL
我希望能够像这样在我的函数中捕获该异常:
def get_a_record(url):
try:
answers = dns.resolver.resolve(url,"A")
except dns.resolver.SERVFAIL:
print("SERVFAIL error for %s" % url)
except dns.resolver.NXDOMAIN:
print("No such domain %s" % url)
except dns.resolver.Timeout:
print("Timed out while resolving %s" % url)
except dns.exception.DNSException:
print("Unhandled exception")
现在我知道在上面的代码片段中 dns 解析器没有 SERVAIL 异常,但我想做的是捕获错误,能够记录它,然后继续我的脚本。是否有使用 dns 解析程序包执行此操作的正确方法,或者我是否需要调用 dig
命令并解析该结果?
编辑
为了澄清,我只使用 dnssec-failed.org 作为示例,因为它导致(我认为的)与我专门寻找的东西相同,但实际上没有任何的活跃例子。那个“东西”是指向不再使用的 IP 地址的域。换句话说,悬挂 NS 记录。
例如,我使用 AWS 借给我的 IP 地址用于某些基于 XYZ 云的应用程序,并在我的 DNS 区域中创建名称到地址的映射记录。如果我决定弃用此服务并且 return 将 ip 返回到云提供商的 ips 池但忘记从区域中删除 DNS 记录,它就会“悬空”。
这就是我要找的东西,我错误地认为 SERVFAIL 是我从 dig domain-with-no-ip.com
这样的查询中得到的响应类型
对造成的混乱表示歉意。
编辑 2
我通过使用我已经注册的域来测试它。为它配置了一个 A 记录,并将它指向一个 Ubuntu EC2 侦听端口 7272 (python3 -m http.server 7272)。等待 5 分钟让区域传播,然后我能够到达我的域,publicly。一切都很好。
然后我停止了实例,稍等片刻,然后重新启动它。回来后它有一个新的 public ip。伟大的。所以此时有一条悬空的A记录供我测试。
所以我在域上进行了挖掘和 nslookup。两者都得到了完美的答案。他们只是简单地指向现在的 old/original public ip。这是有道理的,因为 DNS 记录没有改变。唯一可以观察到的真正改变的是像 curl 这样的东西,它会超时。
因此,除非我的理解仍然错误,否则确实没有一种非常可靠的方法来查找悬空 A 记录,因为基于 n http 超时的逻辑并不一定意味着悬空记录。服务器可能只是 off/down 并且 ip 仍然附加到资源。我的理解是正确的还是我还遗漏了什么?
编辑 3
接受答案是因为即使我的问题稍微演变成其他问题,答案在技术上确实解决了我 post 的原始问题,我认为有理由接受它。
首先,dnssec-failed.org
有域名服务器,但在设计上无法通过 DNSSEC。
因此,对任何执行 DNSSEC 验证的递归名称服务器的简单查询将如预期的那样失败并返回 SERVFAIL
:
$ dig dnssec-failed.org NS +short
(no output)
$ dig dnssec-failed.org NS | grep status: | tail -1
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 46517
但是一旦您禁用 DNSSEC 验证,您就会获得应有的名称服务器:
$ dig dnssec-failed.org NS +cdflag +noall +ans +nottlunits
dnssec-failed.org. 7200 IN NS dns105.comcast.net.
dnssec-failed.org. 7200 IN NS dns101.comcast.net.
dnssec-failed.org. 7200 IN NS dns102.comcast.net.
dnssec-failed.org. 7200 IN NS dns103.comcast.net.
dnssec-failed.org. 7200 IN NS dns104.comcast.net.
现在回到Python部分。
来自 dnspython 的resolve()
是一个高级 API 调用,它执行解析器所做的一切,即它可能从根递归到能够给你一个答案。因此,这个简单的调用可能隐藏了多个问题和响应,因此可能不会让您暴露于真正的潜在问题,但也会使用异常在输出中提供高级 API。
如您在自己的示例中所见,错误消息中的 SERVFAIL
是正确的,但这是一个 NoNameservers
异常,因为代码向注册表名称服务器询问名称服务器列表(有效,父名称服务器中有一个 DS),然后向这些名称服务器中的任何一个请求更多数据,然后它们在 DNSSEC 验证中失败,因此出现最终异常。
我不清楚你对 DNSSEC 错误的立场是什么,如果你不关心它们,或者你是否真的想研究它们并做一些特别的事情。因此,可能需要调整上述解决方案。如果您不在乎,只需记录 NoNameservers
异常并继续,一切都会正常运行,DNSSEC 验证错误将完全像损坏的域一样发生,这是每个设计。
因此,您真的需要以不同于任何其他错误的任何方式处理 DNSSEC 错误吗?为什么你不能捕获 NoNameservers
异常,记录它,然后更进一步?
否则快速(和肮脏的方式),只需解析附加到 NoNameservers
异常的错误消息,如果你看到 SERVFAIL
你可以假设(但不是 100% 确定)它是一个 DNSSEC 问题,至少根据需要走得更远。
如果您确实需要更多详细信息并确定这是一个 DNSSEC 问题,您需要执行与上述 dig
相同的操作,即执行 2 个查询,只是 CD
DNS标志,并比较结果。这意味着要“低于”resolve()
API 并直接使用 dns.query
,例如这样:
>>> import dns, dns.rcode
>>> resolver_ip = '8.8.8.8' # Use any recursive **validating** nameserver that you trust
>>> query=dns.message.make_query('dnssec-failed.org', 'A')
>>> response = dns.query.udp_with_fallback(query, resolver_ip)[0]
>>> response.rcode() == dns.rcode.SERVFAIL
True
# Now checking if disabling DNSSEC resolves the problem and gets us a reply
# If so, it really means there is a DNSSEC problem
>>> print(str(query))
id 65008
opcode QUERY
rcode NOERROR
flags RD
;QUESTION
dnssec-failed.org. IN A
;ANSWER
;AUTHORITY
;ADDITIONAL
>>> query.flags
<Flag.RD: 256>
>>> query.flags = query.flags | dns.flags.CD
>>> query.flags
<Flag.RD|CD: 272>
>>> print(str(query))
id 65008
opcode QUERY
rcode NOERROR
flags RD CD
;QUESTION
dnssec-failed.org. IN A
;ANSWER
;AUTHORITY
;ADDITIONAL
# We enabled flag "CD" aka checking disabled aka please do not do any DNSSEC validation, and now doing the same query as above again:
>>> response = dns.query.udp_with_fallback(query, resolver_ip)[0]
>>> response.rcode() == dns.rcode.SERVFAIL
False
>>> response.rcode() == dns.rcode.NOERROR
True
>>> response.answer[0][0]
<DNS IN A rdata: 69.252.80.75>