使用Python和Twisted的DNS服务器 - 异步操作问题

时间:2013-09-02 16:12:05

标签: python asynchronous dns twisted

我尝试使用Python 2.7创建一个数据库驱动的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])

1 个答案:

答案 0 :(得分: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

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

其他一些观点: