Лучший способ отправить много 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. Простое внесение этого изменения, даже не делая ваш код более масштабируемым, может дать вам прирост производительности для достижения ваших целей.