4

我正在尝试使用 Python 2.7 在 Twisted 中创建一个数据库驱动的 DNS 服务器(专门用于仅处理 MX 记录,并将其他所有内容传递给上游)。下面的代码有效(就获得结果而言),但不是异步操作。相反,任何传入的 DNS 请求都会阻止整个程序接受任何其他请求,直到第一个请求得到答复。我们需要扩大规模,目前我们无法弄清楚我们哪里出了问题。如果有人有一个可行的例子可以分享,或者看到这个问题,我们将永远感激不尽。

import settings
import db    

from twisted.names import dns, server, client, cache
from twisted.application import service, internet
from twisted.internet import defer


class DNSResolver(client.Resolver):
    def __init__(self, servers):
        client.Resolver.__init__(self, servers=servers)

    @defer.inlineCallbacks
    def _lookup_mx_records(self, hostname, timeout):

        # Check the DB to see if we handle this domain.
        mx_results = yield db.get_domain_mx_record_list(hostname)
        if mx_results:
            defer.returnValue(
                [([dns.RRHeader(hostname, dns.MX, dns.IN, settings.DNS_TTL,
                              dns.Record_MX(priority, forward, settings.DNS_TTL))
                    for forward, priority in mx_results]),
                (), ()])

        # If the hostname isn't in the DB, we forward
        # to our upstream DNS provider (8.8.8.8).
        else:
            i = yield self._lookup(hostname, dns.IN, dns.MX, timeout)
            defer.returnValue(i)

    def lookupMailExchange(self, name, timeout=None):
        """
        The twisted function which is called when an MX record lookup is requested.
        :param name: The domain name being queried for (e.g. example.org).
        :param timeout: Time in seconds to wait for the query response. (optional, default: None)
        :return: A DNS response for the record query.
        """

        return self._lookup_mx_records(name, timeout)


# App name, UID, GID to run as. (root/root for port 53 bind)
application = service.Application('db_driven_dns', 1, 1)

# Set the secondary resolver
db_dns_resolver = DNSResolver(settings.DNS_NAMESERVERS)

# Create the protocol handlers
f = server.DNSServerFactory(caches=[cache.CacheResolver()], clients=[db_dns_resolver])
p = dns.DNSDatagramProtocol(f)
f.noisy = p.noisy = False

# Register as a tcp and udp service
ret = service.MultiService()
PORT=53

for (klass, arg) in [(internet.TCPServer, f), (internet.UDPServer, p)]:
    s = klass(PORT, arg)
    s.setServiceParent(ret)

# Run all of the above as a twistd application
ret.setServiceParent(service.IServiceCollection(application))

编辑#1

blakev 建议我可能没有正确使用生成器(这当然是可能的)。但是,如果我稍微简化一下,甚至不使用数据库,我仍然不能一次处理多个 DNS 请求。为了测试这一点,我已经剥离了课程。接下来是我的整个、可运行的测试文件。即使在我的服务器的这个高度精简的版本中,Twisted 在第一个请求到来之前也不会再接受任何请求。

import sys
import logging

from twisted.names import dns, server, client, cache
from twisted.application import service, internet
from twisted.internet import defer


class DNSResolver(client.Resolver):
    def __init__(self, servers):
        client.Resolver.__init__(self, servers=servers)

    def lookupMailExchange(self, name, timeout=None):
        """
        The twisted function which is called when an MX record lookup is requested.
        :param name: The domain name being queried for (e.g. example.org).
        :param timeout: Time in seconds to wait for the query response. (optional, default: None)
        :return: A DNS response for the record query.
        """
        logging.critical("Query for " + name)

        return defer.succeed([
          (dns.RRHeader(name, dns.MX, dns.IN, 600,
              dns.Record_MX(1, "10.0.0.9", 600)),), (), ()
        ])

# App name, UID, GID to run as. (root/root for port 53 bind)
application = service.Application('db_driven_dns', 1, 1)

# Set the secondary resolver
db_dns_resolver = DNSResolver( [("8.8.8.8", 53), ("8.8.4.4", 53)] )

# Create the protocol handlers
f = server.DNSServerFactory(caches=[cache.CacheResolver()], clients=[db_dns_resolver])
p = dns.DNSDatagramProtocol(f)
f.noisy = p.noisy = False

# Register as a tcp and udp service
ret = service.MultiService()
PORT=53

for (klass, arg) in [(internet.TCPServer, f), (internet.UDPServer, p)]:
    s = klass(PORT, arg)
    s.setServiceParent(ret)

# Run all of the above as a twistd application
ret.setServiceParent(service.IServiceCollection(application))


# If called directly, instruct the user to run it through twistd
if __name__ == '__main__':
    print "Usage: sudo twistd -y %s (background) OR sudo twistd -noy %s (foreground)" % (sys.argv[0], sys.argv[0])
4

1 回答 1

2

马特,

我尝试了您的最新示例,并且效果很好。我想你可能测试错了。

在您以后的评论中,您谈到在查找方法中使用 time.sleep(5) 来模拟缓慢的响应。

你不能那样做。它会阻塞反应堆。如果要模拟延迟,请使用 reactor.callLater 触发延迟

例如

def lookupMailExchange(self, name, timeout=None):
    d = defer.Deferred()
    self._reactor.callLater(
        5, d.callback, 
        [(dns.RRHeader(name, dns.MX, dns.IN, 600, 
                       dns.Record_MX(1, "mail.example.com", 600)),), (), ()]
    )
    return d

以下是我的测试方式:

time bash -c 'for n in "google.com" "yahoo.com"; do dig -p 10053 @127.0.0.1 "$n" MX +short +tries=1 +notcp +time=10 & done; wait'

输出显示两个响应都在 5 秒后返回

1 10.0.0.9.
1 10.0.0.9.

real    0m5.019s
user    0m0.015s
sys 0m0.013s

同样,您需要确保对数据库的调用不会阻塞:

其他几点:

于 2013-09-11T13:14:39.873 回答