
Biblioteca de Consulta DNS Rápida e Ferramenta CLI
ZDNS é um resolvedor DNS de alta velocidade e utilitário de linha de comando para realizar medições DNS em larga escala. ZDNS é escrito em Go e contém seu próprio código de resolução recursiva e um cache otimizado para realizar consultas de um conjunto diversificado de nomes. Usamos https://github.com/zmap/dns para construir e analisar pacotes DNS brutos. Para mais informações sobre a arquitetura e desempenho do ZDNS, consulte o seguinte artigo apresentado na ACM's Internet Measurement Conference '22.
[!TIP] O ZDNS Wiki contém informações adicionais sobre o ZDNS e exemplos de casos de uso.
ZDNS pode ser instalado baixando o repositório e executando make install.
git clone https://github.com/zmap/zdns.git
cd zdns
make install
ZDNS consiste em uma biblioteca de resolvedor recursivo e um invólucro CLI.
A biblioteca consiste em uma struct ResolverConfig que conterá todas as opções de configuração para todas as consultas realizadas.
O ResolverConfig é usado para criar 1+ struct(s) Resolver que realizarão todas as consultas. Um Resolver
deve fazer apenas uma única consulta por vez (não é thread-safe) e múltiplas structs Resolver devem ser
usadas para paralelismo. Consulte nossos exemplos para saber como usar a
biblioteca. Módulos são usados para definir o comportamento das consultas.
ZDNS fornece vários tipos de módulos:
Módulos DNS brutos fornecem a resposta DNS bruta do servidor, semelhante ao dig, mas em JSON. Existe um módulo para (quase) cada tipo de registro DNS.
Módulos de consulta fornecem respostas mais úteis quando várias consultas são
necessárias (por exemplo, completar uma consulta A adicional para endereços IP se um NS for
recebido em NSLOOKUP).
Módulos diversos fornecem outros meios adicionais de consultar servidores (por exemplo,
bind.version).
Detalhamos os módulos abaixo:
Os módulos A, AAAA, AFSDB, ANY, ATMA, AVC, AXFR, BINDVERSION, CAA, CDNSKEY, CDS, CERT, CNAME, CSYNC, DHCID, DMARC, DNSKEY, DS, EID, EUI48, EUI64, GID, GPOS, HINFO, HIP, HTTPS, ISDN, KEY, KX, L32, L64, LOC, LP, MB, MD, MF, MG, MR, MX, NAPTR, NID, NINFO, NS, NSAPPTR, NSEC, NSEC3, NSEC3PARAM, NSLOOKUP, NULL, NXT, OPENPGPKEY, PTR, PX, RP, RRSIG, RT, SVCBS, MIMEA, SOA, SPF, SRV, SSHFP, TALINK, TKEY, TLSA, TXT, UID, UINFO, UNSPEC e URI fornecem a resposta DNS bruta em formato JSON, semelhante ao dig.
Por exemplo, o comando:
echo "censys.io" | zdns A
retorna:
{
"name": "censys.io",
"results": {
"A": {
"data": {
"additionals": [
{
"flags": "",
"type": "EDNS0",
"udpsize": 512,
"version": 0
}
],
"answers": [
{
"answer": "104.18.10.85",
"class": "IN",
"name": "censys.io",
"ttl": 300,
"type": "A"
},
{
"answer": "104.18.11.85",
"class": "IN",
"name": "censys.io",
"ttl": 300,
"type": "A"
}
],
"protocol": "udp",
"resolver": "[2603:6013:9d00:3302::1]:53"
},
"duration": 0.285295416,
"status": "NOERROR",
"timestamp": "2024-08-23T13:12:43-04:00"
}
}
}
Respostas DNS brutas frequentemente não fornecem os dados que você deseja. Por exemplo,
uma resposta MX pode não incluir os registros A associados na seção
adicional, exigindo uma consulta adicional. Para resolver essa lacuna e fornecer uma
interface mais amigável, também fornecemos vários módulos de consulta: alookup,
mxlookup e nslookup.
alookup atua de forma semelhante ao nslookup e seguirá registros CNAME.
mxlookup também fará uma consulta A para os endereços IP que correspondem a um registro de exchange.
nslookup também fará uma consulta A/AAAA para endereços IP que correspondem a um registro NS.
Por exemplo,
echo "censys.io" | zdns mxlookup --ipv4-lookup
retorna:
{
"name": "censys.io",
"results": {
"MXLOOKUP": {
"data": {
"exchanges": [
{
"class": "IN",
"ipv4_addresses": [
"209.85.202.27"
],
"name": "alt1.aspmx.l.google.com",
"preference": 5,
"ttl": 300,
"type": "MX"
},
{
"class": "IN",
"ipv4_addresses": [
"142.250.31.26"
],
"name": "aspmx.l.google.com",
"preference": 1,
"ttl": 300,
"type": "MX"
}
]
},
"duration": 0.154786958,
"status": "NOERROR",
"timestamp": "2024-08-23T13:10:11-04:00"
}
}
}
ZDNS também suporta consultas DNS especiais de "debug". Os módulos incluem: BINDVERSION.
ZDNS suporta fornecer entrada em uma variedade de formatos, dependendo do comportamento desejado.
A entrada mais básica é uma lista de nomes separados por novas linhas. Por exemplo:
Do stdin:
echo "google.com\nyahoo.com" | zdns A
cat list_of_domains.txt | zdns A
De um arquivo
zdns A --input-file=list_of_domains.txt
Se você não precisa resolver muitos domínios, fornecer o domínio como argumento CLI, semelhante ao dig, é suportado para facilidade de uso.
Por exemplo:
zdns A google.com --name-servers=1.1.1.1
Equivalente a dig -t A google.com @1.1.1.1
Normalmente, o ZDNS escolherá um servidor de nomes aleatório para cada consulta de domínio a partir de --name-servers. Se, em vez disso, você quiser especificar
um servidor de nomes diferente para cada domínio, pode fazer isso fornecendo pares domainName,nameServerIP separados por novas linhas.
Isso substituirá quaisquer servidores de nomes fornecidos com --name-servers.
Por exemplo:
echo "google.com,1.1.1.1\nfacebook.com,8.8.8.8" | zdns A
Você pode ver o resolver conforme especificado para cada domínio na saída (additionals/answers redigidos por brevidade):
$ echo "google.com,1.1.1.1\nfacebook.com,8.8.8.8" | zdns A
{"name":"google.com","results":{"A":{"data":{"additionals":...,"answers":[...],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.030490042,"status":"NOERROR","timestamp":"2024-09-13T09:51:34-04:00"}}}
{"name":"facebook.com","results":{"A":{"data":{"additionals":[...],"answers":[...],"protocol":"udp","resolver":"8.8.8.8:53"},"duration":0.061365459,"status":"NOERROR","timestamp":"2024-09-13T09:51:34-04:00"}}}
Arquivos de zona (do ICANN CZDS ou similares) podem ser usados como fonte de entrada para o ZDNS com a flag --zone-file. Isso permite analisar arquivos de zona a partir do stdin (padrão) ou de um arquivo com a flag --input-file.
Por padrão, o ZDNS extrairá apenas o nome de cada registro do arquivo de zona. Se você também quiser resolver os nomes referenciados na seção de resposta de tipos de registro como CNAMEs ou registros NS, pode usar a flag CLI --zone-file-include-targets.
Por exemplo, com --zone-file-include-targets e esta entrada de arquivo de zona:
example.com. 3600 IN NS ns1.example.com
tanto example.com quanto ns1.example.com seriam resolvidos.
ZDNS também suporta passar "gatilhos" por linha de entrada que mapeiam linhas de entrada para módulos específicos. Com eles, você pode especificar que certos domínios sejam consultados com módulos específicos.
O formato de entrada é:
domain_name,name_server,trigger,trigger_2,etc, onde nameServer pode estar vazio para usar os servidores de nomes padrão e 1+ gatilhos podem ser especificados.
Um exemplo de arquivo input.csv:
example.com,,a-trigger
google.com,,a-trigger,cname-trigger
example.com,1.1.1.1,aaaa-trigger
yahoo.com
apnews.com,1.1.1.1
E o correspondente multiple.ini:
; Specify Global Options here
[Application Options]
iterative=true
prefer-ipv6-iteration="true"
; List out modules and their respective module-specific options here. A module can only be listed once
[A]
trigger = "a-trigger"
[AAAA]
trigger = "aaaa-trigger"
[CNAME]
trigger = "cname-trigger"
Isso consultará:
example.com com o módulo A usando servidores de nomes padrãogoogle.com com os módulos A + CNAME usando servidores de nomes padrãoexample.com com o módulo AAAA usando o resolvedor 1.1.1.1 da Cloudflareyahoo.com com todos os módulos especificados e usando servidores de nomes padrãoapnews.com com todos os módulos especificados e usando o resolvedor 1.1.1.1 da CloudflareExecutando o comando:
zdns MULTIPLE --multi-config-file="./multiple.ini" --input-file="input.csv"
ZDNS pode operar contra um resolvedor recursivo (por exemplo, um servidor DNS organizacional) [comportamento padrão] ou pode realizar sua própria recursão internamente. Se você estiver realizando um pequeno número de consultas (ou seja, milhões) e usando menos de 10.000 go routines, normalmente é mais rápido usar um dos resolvedores recursivos comuns, como Cloudflare ou Google. Cloudflare é quase sempre mais rápido que o Google. Isso é particularmente verdadeiro se você estiver consultando nomes populares porque eles estão em cache e podem ser respondidos em uma única viagem de ida e volta. Ao usar dezenas de milhares de threads simultâneas, considere realizar a iteração internamente para evitar sobrecarregar e/ou sofrer limitação de taxa do seu resolvedor recursivo.
Para realizar recursão local, execute zdns com a flag --iterative. Quando esta
flag é usada, o ZDNS fará round-robin entre os root servers publicados (por exemplo,
198.41.0.4). No modo iterativo, você pode controlar o tamanho do cache local
especificando --cache-size e o timeout para iterações individuais definindo
--iteration-timeout. A flag --timeout controla o timeout de toda a
resolução para uma dada entrada (ou seja, a soma de todas as etapas iterativas).
O desempenho do ZDNS vem da paralelização massiva usando Go routines leves. Esta arquitetura tem várias ressalvas:
Cada Go routine usa seu próprio socket de rede dedicado. Portanto, você precisa ser
capaz de abrir tantos sockets (em termos de número máximo de descritores de arquivo e
portas efêmeras) quantas threads especificadas (via --threads). Por padrão,
o ZDNS usa 1.000 threads, que é menos que o número máximo padrão do Linux de 1024
FDs abertos. No entanto, é maior que o padrão do Mac OS de 256. Você pode ver
o número máximo de FDs abertos (e, portanto, sockets) permitidos executando ulimit -n. Se
você quiser executar com um número de threads maior que esse número, precisa
aumentar o número de arquivos abertos no nível do sistema operacional. Se você não fizer isso,
encontrará um erro fatal semelhante a FATA[0000] unable to create socketlisten udp <client IP address>:0: socket: too many open files. Se
você quiser executar mais threads do que portas efêmeras disponíveis, precisará
usar vários endereços IP de cliente: --local-addr=A,B,C.
Por padrão, o ZDNS "reutiliza" sockets UDP criando um socket UDP não vinculado para
cada routine leve na inicialização e usando-o para todas as consultas (independentemente
do IP de destino). Isso melhora drasticamente o desempenho porque o ZDNS e o
sistema operacional host não precisam configurar e desmontar um socket para enviar cada
pacote individual (já que consultas/respostas DNS tendem a ser um pacote cada). No entanto,
isso significa que o ZDNS pré-alocará um socket para cada thread na inicialização. Isso pode não
ser ideal se você estiver consultando apenas um pequeno número de nomes. Por exemplo,
se você precisar consultar apenas 100 nomes, mas usar o padrão de 1.000 threads, você
vinculará, mas nunca usará, 900 sockets UDP. Em vez de se preocupar em reciclar
sockets, recomendamos que você especifique um número razoável de threads para seu
caso de uso (já que isso também evita o trabalho de iniciar essas threads em primeiro lugar).
É por isso que, no entanto, você pode obter um erro sobre a incapacidade de abrir um grande
número de sockets mesmo que esteja consultando apenas um único nome. Se for importante
criar um socket novo para cada consulta, você pode desabilitar essa reutilização especificando
--recycle-sockets=false.
Grande parte do desempenho que você verá depende do seu fluxo de trabalho, hardware e de quantos servidores de nomes a carga é distribuída. Se você deseja maximizar o desempenho
para seu fluxo de trabalho/hardware, recomendamos começar com 100 threads e aumentar até começar a ver um aumento nas falhas de resolução de nomes.
Para ajudar com isso, você pode usar --output-file=output.jsonl e grep -v "NOERROR" output.jsonl | wc -l para contar o número de nomes que falharam ao resolver.
As flags que podem ser úteis para ajustar o desempenho são:
--timeout A quantidade máxima de tempo que o ZDNS gastará em um único nome--iteration-timeout A quantidade máxima de tempo que o ZDNS gastará em uma única etapa de iteração (ex: resolvendo google.com na camada .com)--network-timeout A quantidade máxima de tempo que o ZDNS esperará por uma resposta de um servidor de nomes--retries=N Se uma conexão com um servidor de nomes específico falhar em --iterative, o ZDNS tentará novamente com outro servidor de nomes não consultado naquela camada.
As tentativas são por nome, então se --retries=1, o ZDNS tentará novamente um nome contra um novo servidor de nomes uma vez durante seu processo de iteração completo. Se todos os servidores de nomes tiverem sido consultados,
um servidor de nomes aleatório será escolhido.--name-servers A lista de servidores de nomes a serem usados para consultas, principalmente útil com --iterative=falseDNS inclui muitos dados estranhos que nem sempre são úteis. Existem quatro
níveis de detalhamento de resultado: short, normal (padrão), long e trace:
short: Short é a saída de resultado mais concisa. Contém apenas informações sobre as respostasnormal: Normal fornece tudo incluído em short, bem como dados sobre o servidor que respondeulong: Long produz tudo que o servidor incluiu no pacote DNS, incluindo flags.trace: Trace produz tudo de cada etapa do processo de recursãoOs usuários também podem incluir campos adicionais específicos usando a flag
--include-fields e especificando uma lista de campos, por exemplo, --include-fields=flags,resolver.
Campos adicionais são: class, protocol, ttl, resolver, flags, dnssec.
Por padrão, o ZDNS espera receber uma lista de nomes para consultar em um pequeno número de servidores de nomes. Por exemplo:
echo "google.com" | zdns A --name-servers=8.8.8.8,8.8.4.4
No entanto, há momentos em que você deseja consultar o mesmo nome em um grande número de servidores. Isso pode ser feito usando o modo servidor de nomes. Por exemplo:
echo "8.8.8.8" | zdns A --name-server-mode --override-name="google.com"
Aqui, cada linha enviada ao ZDNS recebe uma consulta A para google.com. O ZDNS também
suporta misturar e combinar ambos os modos enviando uma lista separada por vírgulas de
name,nameServer. Por exemplo:
echo "google.com,8.8.8.8" | zdns A enviará uma consulta A para
google.com para 8.8.8.8 independentemente de quais servidores de nomes são especificados pela
flag --name-servers=. Linhas que não especificam explicitamente um servidor de nomes usarão
os servidores especificados pelo SO ou pela flag --name-servers como normalmente aconteceria.
Existe um recurso disponível para realizar uma consulta DNS específica contra todos os servidores de nomes. Por exemplo, você pode querer obter os registros A de todos os servidores de nomes de um determinado domínio. Para fazer isso, você pode:
echo "google.com" | zdns A --all-nameservers
ZDNS suporta o uso de múltiplos módulos de consulta em uma única invocação. Por exemplo, digamos que você queira realizar uma consulta A,
AAAA e MXLOOKUP para um conjunto de domínios e deseja realizá-las com resolução iterativa. Você precisará usar o
módulo MULTIPLE e fornecer um arquivo de configuração com os módulos e flags específicas do módulo que deseja usar.
Por favor, veja zdns --help e zdns <MODULE_NAME> --help para opções Globais e específicas do Módulo que podem ser usadas no arquivo de configuração.
Por exemplo:
cat 1000k_domains.txt | zdns MULTIPLE --multi-config-file="./multiple.ini"
Onde multiple.ini é um arquivo que se parece com:
; Specify Global Options here
[Application Options]
iterative=true
; List out modules and their respective module-specific options here. A module can only be listed once
[MXLOOKUP]
ipv4-lookup = true
; You can use default values and just list modules if you don't need to specify any options
[A]
[AAAA]
Um arquivo de amostra multiple.ini é fornecido em src/cli/multiple.ini
Por padrão, o ZDNS operará com 1.000 go routines leves. Se você não
tomar cuidado, isso sobrecarregará muitos provedores DNS upstream. Sugerimos que
os usuários coordenem com os administradores de rede locais antes de realizar qualquer varredura.
Você pode controlar o número de conexões simultâneas com os argumentos de linha de comando --threads e
--go-processes. Servidores de nomes alternativos podem ser
especificados com --name-servers. O ZDNS alternará entre esses servidores ao
fazer solicitações. Executamos o ZDNS com sucesso com dezenas de milhares de
rotinas leves.
Se o zdns encontrar um tipo de registro que não suporta, ele gerará um registro
de saída com o campo type definido corretamente e uma representação da
estrutura de dados subjacente no campo unparsed_rr. Não confie na
presença ou estrutura deste campo. Este campo (e sua existência) pode mudar
a qualquer momento à medida que expandimos o suporte para tipos de registro adicionais. Se você
se encontrar usando este campo, considere enviar um pull-request adicionando
suporte para parser.
Há um benchmark disponível em benchmark/ que pode ser usado para executar o ZDNS de forma previsível e imprimir algumas estatísticas sobre a execução.
Isso pode ser útil para comparar o desempenho antes e depois de uma alteração no ZDNS.
Veja mais detalhes no README do benchmark.
Se você está interessado em contribuir com o ZDNS, consulte CONTRIBUTING.
ZDNS Copyright 2020 Regents of the University of Michigan
Licenciado sob a Licença Apache, Versão 2.0 (a "Licença"); você não pode usar este arquivo exceto em conformidade com a Licença. Você pode obter uma cópia da Licença em http://www.apache.org/licenses/LICENSE-2.0
A menos que exigido por lei aplicável ou acordado por escrito, o software distribuído sob a Licença é distribuído "COMO ESTÁ", SEM GARANTIAS OU CONDIÇÕES DE QUALQUER TIPO, expressas ou implícitas. Consulte a Licença para o idioma específico que rege as permissões e limitações sob a Licença.
Go fica feliz em usar todos os núcleos de CPU disponíveis e pode usar uma
quantidade tremenda de CPU se você especificar um grande número de threads. A CPU é
usada principalmente para análise e codificação JSON. Se você quiser limitar o número
de núcleos de CPU, pode fazer isso incluindo a flag --go-processes=n ou definindo
a variável de ambiente GOMAXPROCS.
É difícil recomendar uma quantidade precisa de --threads, pois depende de vários
fatores. O gráfico abaixo mostra como um fluxo de trabalho de amostra tem menor tempo de execução, mas maiores taxas de falha na resolução de nomes à medida que o número de threads aumenta.