Макет DNS-сервера с использованием Twisted - PullRequest
6 голосов
/ 24 января 2020

Я пытаюсь написать фиктивный DNS-сервер на основе Twisted для выполнения некоторых тестов.

Вдохновленный этим руководством Я написал очень простой сервер, который просто разрешает все в 127.0.0.1:

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


class MockDNSResolver:

    def _doDynamicResponse(self, query):
        name = query.name.name
        record = dns.Record_A(address=b"127.0.0.1")
        answer = dns.RRHeader(name=name, payload=record)
        authority = []
        additional = []
        return [answer], authority, additional

    def query(self, query, timeout=None):
        print("Incoming query for:", query.name)
        if query.type == dns.A:
            return defer.succeed(self._doDynamicResponse(query))
        else:
            return defer.fail(error.DomainError())


if __name__ == "__main__":
    clients = [MockDNSResolver()]
    factory = server.DNSServerFactory(clients=clients)
    protocol = dns.DNSDatagramProtocol(controller=factory)
    reactor.listenUDP(10053, protocol)
    reactor.listenTCP(10053, factory)
    reactor.run()

Вышеописанное прекрасно работает с dig и nslookup (с другого терминала):

$ dig -p 10053 @localhost something.example.org A +short
127.0.0.1

$ nslookup something.else.example.org 127.0.0.1 -port=10053
Server:     127.0.0.1
Address:    127.0.0.1#10053

Non-authoritative answer:
Name:   something.else.example.org
Address: 127.0.0.1

Я также получаю соответствующие хиты на терминале, на котором запущен сервер:

Incoming query for: something.example.org
Incoming query for: something.else.example.org

Затем я написал следующий фрагмент кода, основанный на этом разделе о выполнении запросов и этом разделе об установке пользовательского resolver :

from twisted.internet import reactor
from twisted.names.client import createResolver
from twisted.web.client import Agent


d = Agent(reactor).request(b'GET', b'http://does.not.exist')
reactor.installResolver(createResolver(servers=[('127.0.0.1', 10053)]))


def callback(result):
    print('Result:', result)


d.addBoth(callback)
d.addBoth(lambda _: reactor.stop())

reactor.run()

Но это не удается (и я не получаю строк в серверном терминале). Похоже, что запросы отправляются не на фиктивный сервер, а на системный сервер:

Result: [Failure instance: Traceback: <class 'twisted.internet.error.DNSLookupError'>: DNS lookup failed: no results for hostname lookup: does.not.exist.
/.../venv/lib/python3.6/site-packages/twisted/internet/_resolver.py:137:deliverResults
/.../venv/lib/python3.6/site-packages/twisted/internet/endpoints.py:921:resolutionComplete
/.../venv/lib/python3.6/site-packages/twisted/internet/defer.py:460:callback
/.../venv/lib/python3.6/site-packages/twisted/internet/defer.py:568:_startRunCallbacks
--- <exception caught here> ---
/.../venv/lib/python3.6/site-packages/twisted/internet/defer.py:654:_runCallbacks
/.../venv/lib/python3.6/site-packages/twisted/internet/endpoints.py:975:startConnectionAttempts
]

Я использую:

  • macOS 10.14.6 Python 3.6.6, витая 18.9.0
  • Linux мята 19.1, Python 3.6.9, витая 19.7.0

Я ценю любую помощь, пожалуйста, дайте мне знать, требуется ли дополнительная информация.

Спасибо!

Ответы [ 2 ]

3 голосов
/ 03 февраля 2020

Решение было следующим:

  • (Клиент) поменять местами порядок строк, которые устанавливают распознаватель, и создать отложенный запрос, как предложено @Hadus. Я думал, что это не имеет значения, поскольку реактор еще не запущен.
  • (Сервер) реализует lookupAllRecords, повторно используя существующий метод _doDynamicResponse.
# server
from twisted.internet import defer, reactor
from twisted.names import dns, error, server


class MockDNSResolver:
    """
    Implements twisted.internet.interfaces.IResolver partially
    """

    def _doDynamicResponse(self, name):
        print("Resolving name:", name)
        record = dns.Record_A(address=b"127.0.0.1")
        answer = dns.RRHeader(name=name, payload=record)
        return [answer], [], []

    def query(self, query, timeout=None):
        if query.type == dns.A:
            return defer.succeed(self._doDynamicResponse(query.name.name))
        return defer.fail(error.DomainError())

    def lookupAllRecords(self, name, timeout=None):
        return defer.succeed(self._doDynamicResponse(name))


if __name__ == "__main__":
    clients = [MockDNSResolver()]
    factory = server.DNSServerFactory(clients=clients)
    protocol = dns.DNSDatagramProtocol(controller=factory)
    reactor.listenUDP(10053, protocol)
    reactor.listenTCP(10053, factory)
    reactor.run()
# client
from twisted.internet import reactor
from twisted.names.client import createResolver
from twisted.web.client import Agent


reactor.installResolver(createResolver(servers=[('127.0.0.1', 10053)]))
d = Agent(reactor).request(b'GET', b'http://does.not.exist:8000')


def callback(result):
    print('Result:', result)


d.addBoth(callback)
d.addBoth(lambda _: reactor.stop())

reactor.run()
$ python client.py
Result: <twisted.web._newclient.Response object at 0x101077f98>

(у меня простой веб-сервер с python3 -m http.server в другом терминале, в противном случае я получаю разумное исключение twisted.internet.error.ConnectionRefusedError).

0 голосов
/ 02 февраля 2020

Вот как вы должны сделать запрос к нему:

from twisted.internet import reactor

from twisted.names import client
resolver = client.createResolver(servers=[('127.0.0.1', 10053)])
d = resolver.getHostByName('does.not.exist')

def callback(result):
    print('Result:', result)

d.addBoth(callback)

reactor.callLater(4, reactor.stop)
reactor.run()

И MockDNSResolver должен быть подклассом twisted.names.common.ResolverBase, и тогда вам нужно только реализовать _lookup.

class MockDNSResolver(common.ResolverBase):
    def _lookup(self, name, cls, type, timeout):
        print("Incoming query for:", name)
        record = dns.Record_A(address=b"127.0.0.5")
        answer = dns.RRHeader(name=name, payload=record)
        return defer.succeed(([answer], [], []))

Полный код: https://pastebin.com/MfGahtAV

...