twisted.names.client 指南

Twisted Names 提供了分层的客户端 API 选择。

在本节中,您将了解

  • 关于高级 client API,

  • 关于如何在 Python shell 中交互式地使用客户端 API(对 DNS 调试和诊断很有用),

  • 关于 IResolverSimpleIResolver 接口,

  • 关于这些接口的各种实现以及何时使用它们,

  • 如何自定义反应器执行主机名解析的方式,

  • 最后,您还将被介绍一些底层 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>], [], [])>

所有 IResolverSimpleIResolver 方法都是异步的,因此返回 deferred

getHostByName(是 IResolverSimple 的一部分)返回一个 IP 地址,而 lookupMailExchange 返回三个 DNS 记录列表。这三个列表包含答案记录、授权记录和附加记录。

注意

  • getHostByName 可能会返回一个 IPv6 地址;与它的标准库等效项(socket.gethostbyname())不同

  • IResolver 包含用于查找每种常见 DNS 记录类型的单独函数。

  • IResolver 包含一个用于发出任意查询的底层 query 函数。

  • names.client 模块 directlyProvides IResolverSimpleIResolver 接口。

  • 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 诊断工具。