创建自定义服务器

内置的 DNS 服务器插件很有用,但 Twisted Names 的精妙之处在于你可以使用 names 组件构建自己的自定义服务器和客户端。

  • 在本节中,你将了解构建简单 DNS 服务器所需的组件。

  • 然后,你将学习如何创建一个自定义 DNS 服务器,该服务器可以动态计算响应。

简单的转发 DNS 服务器

让我们从创建一个简单的转发 DNS 服务器开始,该服务器将所有请求转发到上游服务器(或服务器)。

simple_server.py

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
An example of a simple non-authoritative DNS server.
"""

from twisted.internet import reactor
from twisted.names import client, dns, server


def main():
    """
    Run the server.
    """
    factory = server.DNSServerFactory(
        clients=[client.Resolver(resolv="/etc/resolv.conf")]
    )

    protocol = dns.DNSDatagramProtocol(controller=factory)

    reactor.listenUDP(10053, protocol)
    reactor.listenTCP(10053, factory)

    reactor.run()


if __name__ == "__main__":
    raise SystemExit(main())

在这个示例中,我们向 client.Resolver 实例传递了一个 DNSServerFactory,并将该客户端配置为使用本地 resolv.conf 文件中指定的 上游 DNS 服务器。

还要注意,我们启动了服务器,使其同时监听 UDP 和 TCP 端口。这是 DNS 服务器的标准要求。

你可以使用 dig 测试服务器。例如

$ dig -p 10053 @127.0.0.1 example.com SOA +short
sns.dns.icann.org. noc.dns.icann.org. 2013102791 7200 3600 1209600 3600

动态计算响应的服务器

现在假设我们想要创建一个定制的 DNS 服务器,它通过动态计算结果 IP 地址来响应某些主机名查询,同时将所有其他查询传递到另一个 DNS 服务器。匹配模式 workstation{0-9}+ 的主机名查询将导致一个 IP 地址,其中最后一个字节与工作站编号匹配。

我们将编写一个自定义解析器,将其插入标准客户端解析器之前。自定义解析器将首先被查询。

以下是代码

override_server.py

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
An example demonstrating how to create a custom DNS server.

The server will calculate the responses to A queries where the name begins with
the word "workstation".

Other queries will be handled by a fallback resolver.

eg
    python doc/names/howto/listings/names/override_server.py

    $ dig -p 10053 @localhost workstation1.example.com A +short
    172.0.2.1
"""

from twisted.internet import defer, reactor
from twisted.names import client, dns, error, server


class DynamicResolver:
    """
    A resolver which calculates the answers to certain queries based on the
    query type and name.
    """

    _pattern = "workstation"
    _network = "172.0.2"

    def _dynamicResponseRequired(self, query):
        """
        Check the query to determine if a dynamic response is required.
        """
        if query.type == dns.A:
            labels = query.name.name.split(".")
            if labels[0].startswith(self._pattern):
                return True

        return False

    def _doDynamicResponse(self, query):
        """
        Calculate the response to a query.
        """
        name = query.name.name
        labels = name.split(".")
        parts = labels[0].split(self._pattern)
        lastOctet = int(parts[1])
        answer = dns.RRHeader(
            name=name,
            payload=dns.Record_A(address=b"%s.%s" % (self._network, lastOctet)),
        )
        answers = [answer]
        authority = []
        additional = []
        return answers, authority, additional

    def query(self, query, timeout=None):
        """
        Check if the query should be answered dynamically, otherwise dispatch to
        the fallback resolver.
        """
        if self._dynamicResponseRequired(query):
            return defer.succeed(self._doDynamicResponse(query))
        else:
            return defer.fail(error.DomainError())


def main():
    """
    Run the server.
    """
    factory = server.DNSServerFactory(
        clients=[DynamicResolver(), client.Resolver(resolv="/etc/resolv.conf")]
    )

    protocol = dns.DNSDatagramProtocol(controller=factory)

    reactor.listenUDP(10053, protocol)
    reactor.listenTCP(10053, factory)

    reactor.run()


if __name__ == "__main__":
    raise SystemExit(main())

请注意,DynamicResolver.query 返回一个 Deferred。成功时,它将返回三个 DNS 记录列表(答案、授权、附加),这些记录将被 dns.Message 编码并返回给客户端。失败时,它将返回一个 DomainError,它表示应该将查询分派到列表中的下一个客户端解析器。

注意

回退行为实际上是由 ResolverChain 处理的。

ResolverChain 是其他解析器的代理。它接受一个 IResolver 提供程序列表,并依次查询每个提供程序,直到收到答案,或者直到列表耗尽。

链中的每个 IResolver 都可能返回一个延迟的 DomainError,它表示 ResolverChain 应该查询下一个链式解析器。

The DNSServerFactory 构造函数接受一个权威解析器、缓存和客户端解析器列表,并确保它们以正确的顺序添加到 ResolverChain 中。

让我们使用 dig 来查看此服务器如何响应我们指定的模式匹配的请求

$ dig -p 10053 @127.0.0.1 workstation1.example.com A +short
172.0.2.1

$ dig -p 10053 @127.0.0.1 workstation100.example.com A +short
172.0.2.100

如果我们发出不匹配模式的请求

$ dig -p 10053 @localhost www.example.com A +short
93.184.216.119

进一步阅读

为了简单起见,上面的示例使用了 reactor.listenXXX API。但是,如果使用 Twisted 应用程序 APITwisted 插件系统twistd,你的应用程序将更加灵活。阅读 names.tap 的源代码,了解 twistd names 插件的工作原理。