Лучший способ отправить много DNS-запросов с витой структурой (через UDP)?

В настоящее время я изучаю скрученную среду и пытаюсь создать асинхронный DNS-преобразователь, используя twisted.names.client.Resolver а также twisted.names.client.getHostByName,

Скрипт должен обрабатывать субдомены, запрашивая авторитетные серверы имен. 10000-50000 в секунду одновременных подключений - мой минимальный порог, чтобы считать инструмент пригодным для моих намерений.

Мои вопросы:

  • Является ли витой подходит / подходит для таких начинаний?
  • Насколько сильна общая производительность Python/Twisted по сравнению с C для таких проектов? Я предполагаю, что витая не совсем подходит для такого рода идей, и у внутреннего управления реактором есть большие издержки, когда речь идет о многих соединениях...
  • Такие проекты, как masscan, очень быстрые. Автору удается отправить 2 млн. пакетов / секунду (со специальными драйверами PF_RING и более). В настоящее время я выясняю, как он это делает, но я надеюсь, что мне не нужно идти по этому пути, потому что я хочу остаться с витой.

Становление конкретным: приведенный ниже скрипт - моя самая первая попытка, но она работает не так быстро, как хотелось бы.

Я настоятельно предполагаю, что мой подход совершенно неверен. Если вы называете нижний скрипт так:

[nikolai@niko-arch subdomains]$ python2 subdomains.py -n50 nytimes.com       
www ==> ``170.149.168.130``
blog ==> ``170.149.168.153``
cs ==> ``199.181.175.242``
my ==> ``170.149.172.130``
blogs ==> ``170.149.168.153``
search ==> ``170.149.168.135``
cn ==> ``61.244.110.199``
feeds ==> ``170.149.172.130``
app ==> ``54.243.156.140``
games ==> ``184.73.175.199``
mail ==> ``170.149.172.135``
up ==> ``107.20.203.136``
tv ==> ``170.149.168.135``
data ==> ``174.129.28.73``
p ==> ``75.101.137.16``
open ==> ``170.149.168.153``
ts ==> ``170.149.97.51``
education ==> ``170.149.168.130``
wap ==> ``170.149.172.163``
m ==> ``170.149.172.163``

В большинстве случаев все работает нормально с 50 субдоменами. Но когда я указываю -n1000 (и, следовательно, 1000 запросов на обновление), это занимает очень очень много времени (5 минут и более), и реактор выдает все виды странных ошибок, таких как twisted.internet.error.DNSLookupError и twisted.internet.defer.TimeoutError (Пример: Failure: twisted.internet.defer.TimeoutError: [Query('blogger.l.google.com', 255, 1)]). Обычно он просто зависает и не заканчивается.

Я ожидаю, что каждый несуществующий поддомен получит twisted.names.error.DNSNameError или, в случае, если поддомен существует, допустимый ответ записи ресурса A или AAAA, но не DNSLookupError, как указано выше.

Кто-нибудь может дать мне подсказку, что я делаю неправильно? Обычно epoll() легко может отправлять более 1000 запросов (несколько лет назад я сделал то же самое на дейтаграммах C и 10000 udp, которые были отправлены в считанные секунды). Так какую часть скручивания я не понял правильно?

Является ли collectResults() неверным? Я не знаю, что я делаю не так..

Заранее большое спасибо за все ответы!

# Looks promising: https://github.com/zhangyuyan/github
# https://github.com/zhangyuyan/github/blob/01dd311a1f07168459b222cb5c59ac1aa4d5d614/scan-dns-e3-1.py

import os
import argparse
import exceptions

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

def printResults(results, subdomain):
    """
    Print the ip address for the successful query.
    """
    return '%s ==> ``%s``' % (subdomain, results)

def printError(failure, subdomain):
    """
    Lookup failed for some reason, just catch the DNSNameError and DomainError.
    """
    reason = failure.trap(error.DNSNameError, error.DomainError, terr.DNSLookupError, defer.TimeoutError) # Subdomain wasn't found
    print(failure)
    return reason

def printRes(results):
    for i in results:
        if not isinstance(i, type): # Why the heck are Failure objects of type 'type'???
            print(i)
    reactor.stop()
    global res
    res = results

def get_args():
    parser = argparse.ArgumentParser(
        description='Brute force subdomains of a supplied target domain. Fast, using async IO./n')
    parser.add_argument('target_domain', type=str, help='The domain name to squeeze the subdomains from')
    parser.add_argument('-r', '--default-resolver', type=str, help='Add here the ip of your preferred DNS server')
    parser.add_argument('-n', '--number-connections', default=100, type=int, help='The number of file descriptors to acquire')
    parser.add_argument('-f', '--subdomain-file', help='This file should contain the subdomains separated by newlines')
    parser.add_argument('-v', '--verbosity', action='count', help='Increase the verbosity of output', default=0)

    args = parser.parse_args()

    if args.number_connections > 1000:
        # root privs required to acquire more than 1024 fd's
        if os.geteuid() != 0:
            parser.error('You need to be root in order to use {} connections'.format(args.number_connections))
    if not args.default_resolver:
        # Parse /etc/resolv.conf
        args.default_resolver = [line.split(' ')[1].strip() for line in open('/etc/resolv.conf', 'r').readlines() if 'nameserver' in line][0]
    return args

def main(args=None):
    if args:
        args = args
    else:
        args = get_args()

    subs = [sub.strip() for sub in open('subs.txt', 'r').readlines()[:args.number_connections]]

    # use openDNS servers
    r = client.Resolver('/etc/resolv.conf', servers=[('208.67.222.222', 53), ('208.67.220.220', 53)])
    d = defer.gatherResults([r.getHostByName('%s.%s' % (subdomain, args.target_domain)).addCallbacks(printResults, printError, callbackArgs=[subdomain], errbackArgs=[subdomain]) for subdomain in subs])
    d.addCallback(printRes)
    reactor.run()

if __name__ == '__main__':
    main()

1 ответ

Решение

То, как вы это делаете, заключается в том, чтобы буферизовать все запросы субдоменов в один гигантский список, затем сделать все запросы, затем буферизовать ответы на запросы в одном гигантском списке и распечатать этот список. Поскольку вы, вероятно, просто хотите напечатать разрешения имен по мере их поступления, вам следует вместо этого планировать синхронизированные вызовы для выполнения запросов, возможно, с очень коротким интервалом в пакетах определенного размера.

Кроме того, если вы заинтересованы в высокопроизводительном Python, вы должны использовать PyPy, а не CPython. Простое внесение этого изменения, даже не делая ваш код более масштабируемым, может дать вам прирост производительности для достижения ваших целей.

Другие вопросы по тегам