twisted.names.client 指南¶
Twisted Names 提供了分层的客户端 API 选择。
在本节中,您将了解
关于高级
client
API,关于如何在 Python shell 中交互式地使用客户端 API(对 DNS 调试和诊断很有用),
关于
IResolverSimple
和IResolver
接口,关于这些接口的各种实现以及何时使用它们,
如何自定义反应器执行主机名解析的方式,
最后,您还将被介绍一些底层 API。
使用全局解析器¶
从 Twisted 发出 DNS 查询的最简单方法是使用 names.client
中的模块级函数。
以下是一个示例,展示了在交互式 twisted.conch
shell 中生成的一些 DNS 查询。
注意
twisted.conch
shell 启动一个 reactor
,以便可以交互式地运行异步操作,并且它打印已触发的 deferred
的当前结果。
您会注意到,以下示例中返回的 deferred
没有立即得到结果 - 它们正在等待来自 DNS 服务器的响应。
因此,我们稍后输入 _
(默认变量),以在收到答案并触发 deferred
后显示 deferred
的值。
$ python -m twisted.conch.stdio
>>> from twisted.names import client
>>> client.getHostByName('www.example.com')
<Deferred at 0xf5c5a8 waiting on Deferred at 0xf5cb90>
>>> _
<Deferred at 0xf5c5a8 current result: '2606:2800:220:6d:26bf:1447:1097:aa7'>
>>> client.lookupMailExchange('twistedmatrix.com')
<Deferred at 0xf5cd40 waiting on Deferred at 0xf5cea8>
>>> _
<Deferred at 0xf5cd40 current result: ([<RR name=twistedmatrix.com type=MX class=IN ttl=1s auth=False>], [], [])>
所有 IResolverSimple
和 IResolver
方法都是异步的,因此返回 deferred
。
getHostByName
(是 IResolverSimple
的一部分)返回一个 IP 地址,而 lookupMailExchange
返回三个 DNS 记录列表。这三个列表包含答案记录、授权记录和附加记录。
注意
getHostByName
可能会返回一个 IPv6 地址;与它的标准库等效项(socket.gethostbyname()
)不同IResolver
包含用于查找每种常见 DNS 记录类型的单独函数。IResolver
包含一个用于发出任意查询的底层query
函数。names.client
模块directlyProvides
IResolverSimple
和IResolver
接口。createResolver
构造一个全局解析器,它对底层操作系统使用的相同 DNS 源和服务器执行查询。也就是说,它将使用在本地
resolv.conf
文件中找到的 DNS 服务器 IP 地址(如果操作系统提供了这样的文件),并且它将使用特定于操作系统的hosts
文件路径。
一个简单的示例¶
在本节中,您将了解如何使用 IResolver
接口编写一个用于执行 反向 DNS 查找 的实用程序。 dig 也可以做到这一点,所以让我们先检查一下它的输出
$ dig -x 127.0.0.1
...
;; QUESTION SECTION:
;1.0.0.127.in-addr.arpa. IN PTR
;; ANSWER SECTION:
1.0.0.127.in-addr.arpa. 86400 IN PTR localhost.
...
如您所见,dig
执行了具有以下属性的 DNS 查询
名称:
1.0.0.127.in-addr.arpa.
类:
IN
类型:
PTR
名称 是一个反向域名,它是通过反转 IPv4 地址并将其附加到特殊的 in-addr.arpa 父域名来派生的。所以,让我们编写一个函数来从 IP 地址创建反向域名。
def reverseNameFromIPAddress(address):
return ".".join(reversed(address.split("."))) + ".in-addr.arpa"
我们可以从 python shell 测试输出
>>> reverseNameFromIPAddress('192.0.2.100')
'100.2.0.192.in-addr.arpa'
我们将使用 twisted.names.client.lookupPointer()
来执行实际的 DNS 查找。所以让我们检查一下 lookupPointer
的输出,以便我们可以设计一个函数以类似于 dig
的样式格式化和打印其结果。
注意
lookupPointer
是一个异步函数,因此我们将在这里使用交互式 twisted.conch
shell。
$ python -m twisted.conch.stdio
>>> from twisted.names import client
>>> from reverse_lookup import reverseNameFromIPAddress
>>> d = client.lookupPointer(name=reverseNameFromIPAddress('127.0.0.1'))
>>> d
<Deferred at 0x286b170 current result: ([<RR name=1.0.0.127.in-addr.arpa type=PTR class=IN ttl=86400s auth=False>], [], [])>
>>> d.result
([<RR name=1.0.0.127.in-addr.arpa type=PTR class=IN ttl=86400s auth=False>], [], [])
lookupPointer
的延迟结果是一个包含三个记录列表的元组;答案、授权和附加。实际记录是一个 Record_PTR
实例,可以通过 RRHeader
.payload
属性访问。
>>> recordHeader = d.result[0][0]
>>> recordHeader.payload
<PTR name=localhost ttl=86400>
所以,现在我们已经找到了我们需要的信息,让我们创建一个函数来提取第一个答案并打印域名和记录有效负载。
def printResult(result):
answers, authority, additional = result
if answers:
a = answers[0]
print(f"{a.name.name} IN {a.payload}")
让我们测试一下输出
>>> from twisted.names import dns
>>> printResult(([dns.RRHeader(name='1.0.0.127.in-addr.arpa', type=dns.PTR, payload=dns.Record_PTR('localhost'))], [], []))
1.0.0.127.in-addr.arpa IN <PTR name=localhost ttl=None>
很好!现在我们可以将这些部分组装在一个 main
函数中,我们将使用 twisted.internet.task.react()
调用它。以下是完整的脚本。
listings/names/reverse_lookup.py
import sys
from twisted.internet import task
from twisted.names import client
def reverseNameFromIPAddress(address):
return ".".join(reversed(address.split("."))) + ".in-addr.arpa"
def printResult(result):
answers, authority, additional = result
if answers:
a = answers[0]
print(f"{a.name.name} IN {a.payload}")
def main(reactor, address):
d = client.lookupPointer(name=reverseNameFromIPAddress(address=address))
d.addCallback(printResult)
return d
task.react(main, sys.argv[1:])
输出如下所示
$ python reverse_lookup.py 127.0.0.1
1.0.0.127.in-addr.arpa IN <PTR name=localhost ttl=86400>
注意
您可以在 RFC 1034#section-5.2.1 中阅读有关反向域名的更多信息。
在本例中,我们忽略了 IPv6 地址,但您可以在 RFC 3596#section-2.5 中阅读有关反向 IPv6 域名的更多信息,并且该示例可以轻松扩展以支持它们。
您也可以考虑使用 netaddr,它可以生成反向域名,并且还包括复杂的 IP 网络和 IP 地址处理。
此脚本只打印第一个答案,但有时您会由于 CNAME 间接而获得多个答案,例如在无类反向区域的情况下。
所有查找和响应都是异步处理的,因此该脚本可以扩展以并行执行数千个反向 DNS 查找。
接下来,您应该研究 ../examples/multi_reverse_lookup.py
,它扩展了此示例以执行 IPv4 和 IPv6 地址,并且可以并行执行多个反向 DNS 查找。
创建新的解析器¶
现在假设我们想要创建一个 DNS 客户端,它将查询发送到特定的服务器(或服务器)。
在这种情况下,我们直接使用 client.Resolver
并向它传递一个首选服务器 IP 地址和端口列表。
例如,假设我们想要使用免费的 Google DNS 服务器查找名称
$ python -m twisted.conch.stdio
>>> from twisted.names import client
>>> resolver = client.createResolver(servers=[('8.8.8.8', 53), ('8.8.4.4', 53)])
>>> resolver.getHostByName('example.com')
<Deferred at 0x9dcfbac current result: '93.184.216.119'>
这里我们使用的是 Google DNS 服务器 IP 地址和标准 DNS 端口(53)。
在反应器中安装解析器¶
您还可以使用 IReactorPluggableNameResolver
接口将自定义解析器安装到反应器中。
每当反应器需要解析主机名时,它都会使用其安装的解析器;例如,当您向 connectTCP
提供主机名时。
这是一个简短的示例,展示了如何为全局反应器安装一个替代解析器。
from twisted.internet import reactor
from twisted.names import client
reactor.installResolver(client.createResolver(servers=[('8.8.8.8', 53), ('8.8.4.4', 53)]))
之后,反应器请求的所有主机名查找将被发送到 Google DNS 服务器,而不是本地操作系统。
注意
默认情况下,反应器使用操作系统提供的 POSIX
gethostbyname
函数,但
gethostbyname
是一个阻塞函数,因此必须在线程池中调用它。如果您想了解更多关于默认线程解析器的工作原理,请查看
ThreadedResolver
。
更低级的 API¶
以下是如何直接使用 DNSDatagramProtocol
的示例。
from twisted.internet import task
from twisted.names import dns
def main(reactor):
proto = dns.DNSDatagramProtocol(controller=None)
reactor.listenUDP(0, proto)
d = proto.query(('8.8.8.8', 53), [dns.Query('www.example.com', dns.AAAA)])
d.addCallback(printResult)
return d
def printResult(res):
print('ANSWERS: ', [a.payload for a in res.answers])
task.react(main)
在如此低级别工作的缺点是,您需要自己处理查询失败,方法是手动重新发出查询或使用基于流的 dns.DNSProtocol
发出后续 TCP 查询。
这些事情由 client
中的更高级别 API 自动处理。
还要注意,在这种情况下,dns.DNSDatagramProtocol.query
的延迟结果是一个 dns.Message
对象,而不是 DNS 记录列表。
进一步阅读¶
查看 Twisted Names Examples,它演示了如何使用客户端 API 创建有用的 DNS 诊断工具。