DNS Recursivo Anycast com Hyperlocal
Introdução
Você sabe como funciona a Internet? Essa é uma pergunta que meu amigo Thiago Ayub sempre faz aos seus candidatos à vagas de emprego e não importa o quanto tenham de experiência em Engenharia de Redes, todos sempre travam nesse momento. Todos estão sempre prontos e preparados para resolver os problemas mais cabeludos em BGP, OSPF, MPLS, etc mas travam com essa simples pergunta. Para contextualizar e visualizarmos melhor vamos nos atentar à imagem abaixo e uma explicação simplificada de como funciona:
Tudo começa com um usuário sentado confortavelmente e querendo acessar um conteúdo disponível na Internet. Ele digita em seu navegador preferido a URL: https://wiki.ispconfig.com.br, nesse momento seta 1, o navegador irá requisitar do DNS Recursivo utilizado pelo usuário, qual o IP responde pelo hostname wiki.ispup.com.br. Isso porque todos os acessos se dão na Internet através do IP Address e não através do hostname. Imaginem se tivéssemos que decorar os IPs de todos os sites e serviços que quiséssemos acessar na Internet? Só que a primeira vez que consultamos um hostname, o DNS Recursivo não tem a resposta de imediato porque ele não é o dono dessa informação. Então ele faz o trabalho de buscar e consultar o DNS Autoritativo, responsável por aquele domínio em questão ispup.com.br e vemos isso na seta 2. O DNS Autoritativo sabe quais são os IPs que respondem pelo hostname wiki.ispup.com.br e devolve a informação para o DNS Recursivo, seta 3. Na sequência, seta 4, o DNS Recursivo devolve para o navegador do usuário, o IP Address do wiki.ispup.com.br e que pode ser mais de um e de famílias diferentes como IPv4 e IPv6. Cabe ao navegador decidir por qual acessar mas normalmente a preferência é sempre por IPv6, se houver conectividade IPv6 entre o usuário e o conteúdo acessado. Por último, seta 5, o navegador faz o acesso ao site wiki.ispup.com.br através do IP Address.
Como que se dá a comunicação entre os DNS(s) Recursivos e Autoritativos? Como que o navegador do usuário, após receber o IP do site, consegue chegar no servidor que tem o conteúdo? Isso só é possível devido ao protocolo chamado BGP (Border Gateway Protocol), todos os caminhos que conhecemos como rotas de destino, são anunciadas por milhares de participantes na Internet conhecidos como AS (Autonomous System), esses participantes se interligam para disponibilizar conteúdos e acessos pelo mundo aos milhares de usuários. É uma imensa rede colaborativa formada por Empresas, Universidades, Governos e todos que queiram se interconectar. Percebam que sem o BGP, que serve de caminho para chegarmos nos conteúdos e sem o DNS (Domain Name System) para traduzir o hostname para o IP Address, a Internet não funcionaria e por isso precisamos cuidar muito bem desses dois serviços.
Mas não acaba por aí. O DNS Recursivo tem um papel muito importante para o Provedor de Internet e que envolve segurança, qualidade de acesso à Internet e a disponibilidade do serviço entregue ao cliente. Quando bem configurado acelera as consultas dos acessos graças ao seu cache interno, mas para que isso seja percebido pelo assinante, é necessário que esteja o mais próximo possível do seu cliente.
Um erro que destrói a qualidade do nosso serviço
Um erro muito comum que muitas operadoras cometem é utilizar DNS Recursivo externo, como o 8.8.8.8, 1.1.1.1 e outros, para seus clientes. Quanto mais próximo dos seus clientes, mais qualidade de serviço estará entregando eles. Conteúdos serão entregues mais rapidamente pois serão resolvidos e armazenados em caches locais e não consultados remotamente na Internet. Para falar mais sobre isso, te convido leitor desse documento, que assista essa palestra do Thiago Ayub no GTER 51/GTS 37 (2022) 8.888 MOTIVOS PARA NÃO USAR DNS RECURSIVO EXTERNO EM SEU AS: https://www.youtube.com/watch?v=_70OhD-g37s&t=2219s
Objetivo
O objetivo desta documentação não é te ensinar tudo sobre DNS, BGP, OSPF e nem tão pouco sobre GNU/Linux e sim te mostrar um exemplo de servidor DNS Recursivo implementado pensando em segurança, qualidade e resiliência. Usaremos em todas as nossas documentações o Debian GNU/Linux, por ser uma distribuição que considero uma obra de arte criada por uma enorme comunidade séria, com vasta experiência de anos, qualidade no empacotamento dos programas, estável e com uma equipe de segurança excelente e ativa. Caso você leitor, utilize alguma outra distribuição GNU/Linux, todo conteúdo apresentado aqui pode ser aplicado em outras distros, desde que respeitando as particularidades de cada uma.
Aqui construiremos um sistema do tipo Anycast, ou seja, terás o serviço rodando em diversas localidades da sua Rede utilizando o mesmo endereçamento IP e que atenderá seu cliente mais próximo. Em caso de falhas, seus clientes automaticamente e de forma transparente continuarão consultando o DNS mais próximo deles. Para que ele funcione dessa forma você precisará ter uma Rede OSPF implementada no seu Provedor Internet ou algum outro protocolo como por exemplo o ISIS, mas esse documento não irá abordar o ISIS. Também utilizaremos o Hyperlocal como recurso adicional para gerar algumas proteções de segurança e velocidade na resposta relacionada aos servidores de DNS Raiz da Internet.
Diagrama
Para exemplificar nosso servidor de DNS Recursivo, usaremos como base das explicações um diagrama demonstrando o uso do DNS Recursivo em uma Rede fictícia. Adotaremos IPs privados e reservados para demonstrar todo o ambiente do Provedor de Internet.
Nesse diagrama podemos observar alguns detalhes técnicos como por exemplo: existem 3 servidores de DNS Recursivo posicionados em locais diferentes, que poderiam estar em bairros diferentes e até em cidades diferentes. Em cada servidor teremos 2 loopbacks com os IPs:
10.10.10.10/32
10.10.9.9/32
Esses IPs serão entregues pelos concentradores PPPoE ou IPoE (BNG) para seus clientes como DNS primário e secundário. Podemos usar IPs privados como DNS primário e secundário em um ambiente real? Sim podemos, desde que não sejam IPs que possam ter problemas com as redes privadas dos clientes. Ex.: rede do cliente usando 192.168.0.0/24. Se entregarmos o DNS sendo 192.168.0.10 e 192.168.0.20 teremos problemas e o cliente ficará sem Internet, porque 192.168.0.10 e 192.168.0.20 fazem parte da rede 192.168.0.0/24.
Agora entregando 10.10.10.10 e 10.10.9.9 não teríamos problemas com a rede 192.168.0.0/24.
Motivos para usarmos IPs privados:
- O principal motivo está relacionado com a segurança, uma vez que sendo um IP privado, não pode sofrer ataques DDoS direcionados diretamente para ele, vindos da Internet.
- Nem mesmo o cliente da sua rede conhece os IPs públicos utilizados para recursividade na Internet.
- Memorizar os IPs 10.10.10.10 e 10.10.9.9 é tão fácil quanto memorizar o 8.8.8.8 e o 1.1.1.1. Mais fácil para o seu técnico guardar essa informação e utilizar onde for necessário.
Cada servidor DNS Recursivo possui um IPv4 público, aqui representado por 198.18.x.x/27 e um IPv6 global representado por um IP dentro do prefixo 2001:db8::/32. Cada servidor precisa ter os seus próprios IPs e são através destes IPs que as consultas de DNS serão realizadas na Internet.
Nessa topologia usando Anycast, o cliente será sempre atendido pelo DNS Recursivo mais próximo, desde que os pesos no OSPF estejam ajustados corretamente.
Dados do servidor
Podemos utilizar um sistema virtualizado ou não. Sistemas virtualizados são bem vindos pois são mais simples quando precisamos fazer backups, levantar outros sistemas sem complicações e se precisarmos restaurar rapidamente algum sistema que ficou indisponível por algum motivo. A configuração abaixo tem capacidade para atender algo em torno a 50.000 assinantes ou mais. O DNS Recursivo é um serviço que pode ser utilizado até mesmo em um Raspberry Pi e atender operações pequenas, nesse caso com o intuito de economizar energia e espaço. Nosso foco aqui é montar uma rede de DNS Recursivo Anycast com HyperLocal. Como comentei acima o servidor deve ficar o mais próximo dos clientes para termos a menor latência possível e sempre menor que 5ms entre o cliente e o servidor.
CPU | Memória | Disco | Sistema |
---|---|---|---|
2.4Ghz 6 cores | 16G DDR4 | 30G | Debian 11 amd64 (Bullseye) |
Softwares utilizados
- Debian 11 amd64 (Bullseye) instalação mínima.
- FRRouting.
- Unbound.
- IRQBalance.
- Chrony (NTP/NTS).
- Shell script em bash.
Funcionalidades que teremos
- Sistema em Anycast.
- Hyperlocal.
- Controle de acesso por ACL.
- RPZ (Response Policy Zone).
- Bloqueio de consultas do tipo ANY.
- QNAME minimization habilitado. (habilitado por default no Unbound)
- Recursividade em IPv4 e IPv6.
- DNSSEC habilitado.
- DoH (DNS over HTTPS) habilitado.
Monitoramento
O monitoramento é algo bem específico e não é o foco deste documento mas é extremamente importante que você monitore seus servidores de DNS por alguma ferramenta como o Zabbix. Aqui mostrarei apenas como enviar as informações para o Zabbix. Algumas coisas que você deveria monitorar nos servidores de DNS Recursivo:
- Serviço do unbound parou.
- Perda de pacotes.
- Latência alta de pacotes.
- Lentidão na resolução de queries.
- CPU alta.
- Load alto.
- Memória com uso alto.
- Disco com pouco espaço.
- Queda brusca nas queries.
- A recursividade parou de funcionar.
- A recursividade voltou a funcionar.
Este abaixo é um exemplo de monitoramento de um sistema de DNS Recursivo que atende 50.000 assinantes:
Configurando a Rede
Nossa documentação será baseada no diagrama apresentado acima e por isso configuraremos apenas um dos três servidores, porque os outros serão configurados da mesma forma, só que com dados diferentes. Para tanto assumirei que já temos um sistema Debian instalado com o mínimo de pacotes e somente com sshd, para que possamos acessar remotamente mais tarde. Não instale um ambiente gráfico no servidor, você não deve querer fazer isso por diversos motivos e os principais: primeiro porque não é um Desktop e segundo porque o ambiente gráfico devoraria toda a memória com recursos que não seriam úteis aqui.
Em /etc/network/interfaces deixaremos assim:
# This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). source /etc/network/interfaces.d/* # The loopback network interface auto lo iface lo inet loopback auto lo:0 iface lo:0 inet static address 10.10.10.10/32 auto lo:1 iface lo:1 inet static address 10.10.9.9/32 # The primary network interface auto ens18 iface ens18 inet static address 198.18.1.10/27 gateway 198.18.1.1 iface ens18 inet6 static address 2001:db8::faca:198:18:1:10/64 gateway 2001:db8::faca:198:18:1:1 # The secondary network interface auto ens18:0 iface ens18:0 inet static address 172.16.0.6/30
Nesse cenário temos as duas loopbacks com os IPs 10.10.10.10 e 10.10.9.9 que serão anunciados via OSPF para a rede e serem entregues aos clientes via BNG. Os IPs 198.18.1.10 e 2001:db8::faca:198:18:1:10 serão usados para fazerem a recursividade na Internet tanto em IPv4 quanto em IPv6. Esses IPs não devem ser divulgados para clientes; os IPs públicos são dedicados apenas para essa finalidade.
Configuração dos repositórios Debian
Deixe o arquivo /etc/apt/sources.list conforme abaixo:
deb http://security.debian.org/debian-security bullseye-security main contrib non-free deb http://deb.debian.org/debian bullseye main non-free contrib deb http://deb.debian.org/debian bullseye-updates main contrib non-free deb http://deb.debian.org/debian bullseye-backports main contrib non-free
Após a configuração vamos instalar alguns pacotes necessários e outros úteis:
# apt update && apt full-upgrade # apt install net-tools nftables htop iotop sipcalc tcpdump curl gnupg rsync wget host dnsutils mtr-tiny bmon sudo tmux whois ethtool dnstop
Fazendo algum tuning no sistema
Em /etc/sysctl.conf adicionamos no final do arquivo essas instruções:
net.core.rmem_max = 2147483647 net.core.wmem_max = 2147483647 net.ipv4.tcp_rmem = 4096 87380 2147483647 net.ipv4.tcp_wmem = 4096 65536 2147483647 net.netfilter.nf_conntrack_buckets = 512000 net.netfilter.nf_conntrack_max = 4096000 vm.swappiness=10
Estamos fazendo algumas melhorias de memória, algumas relacionadas a conntrack porque se for usar um filtro de pacotes stateful, como o Netfilter/IPTables ou Netfilter/NFTables, o valor default da tabela é pequeno e dependendo da situação, se estourar essa tabela, as consultas de DNS terão problemas também. O DNS Recursivo não deve ficar aberto para qualquer um na Internet, ele deve ser liberado apenas para seus clientes. Podemos fazer através das ACLs do Unbound e pelo filtro de pacotes. O último parâmetro diz respeito ao uso de swap, por padrão o Debian permite o uso de swap após 40% do uso da memória, nesse caso estamos dizendo para o sistema usar o swap com 90% de uso da memória.
Precisamos adicionar o módulo nf_conntrack em /etc/modules para que seja carregado em tempo de boot, senão os parâmetros de conntrack que colocamos em /etc/sysctl.conf não serão carregados.
# echo nf_conntrack >> /etc/modules # modprobe nf_conntrack # sysctl -p
Instalando o FRRouting
O FRRouting é o programa que usaremos para fazer os anúncios das nossas loopbacks via OSPF. Nesse documento usaremos a versão 8.x e para isso precisaremos configurar o repositório oficial do FRRouting e instalar os pacotes:
# echo "deb https://deb.frrouting.org/frr bullseye frr-8" > /etc/apt/sources.list.d/frr.list # curl -s https://deb.frrouting.org/frr/keys.asc | apt-key add - # apt update # apt install frr frr-doc frr-pythontools
Aconselho depois de instalar os pacotes, marcá-los para não atualizar juntamente com os demais pacotes, isso é para evitar de ocorrer alguma atualização no FRRouting, que torne o serviço instável por algum motivo. Não que isso vá ocorrer, mas é melhor fazer essa atualização quando realmente for necessário.
# apt-mark hold frr frr-doc frr-pythontools
Após esse comando acima, o sistema manterá a instalação original do pacote intacta. Para desbloquear basta executar o comando abaixo:
# apt-mark unhold frr frr-doc frr-pythontools
Removendo o APPARMOR
O APPARMOR às vezes causa mais problemas que solução e se não for fazer uma completa configuração nele, é melhor desabilitá-lo. Para fazer isso efetivamente, o procedimento é esse abaixo:
# mkdir -p /etc/default/grub.d # echo 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT apparmor=0"' | tee /etc/default/grub.d/apparmor.cfg # update-grub # reboot
Instalando o Unbound
Nesse momento ainda não iremos configurar o Unbound, apenas instalar o pacote e acertar o ambiente:
# apt install unbound # mkdir -p /var/log/unbound # touch /var/log/unbound/unbound.log # chown -R unbound:unbound /var/log/unbound/ # cd /etc/unbound # wget -c ftp://FTP.INTERNIC.NET/domain/named.cache # systemctl restart unbound
Configurando o Zabbix Agent para envio dos dados do Unbound para o Zabbix
Nessa parte, após você instalar o Zabbix Agent correto para o seu monitoramento, que vai depender da versão que você utiliza do Zabbix Server, irei mostrar como adicionar os dados do Unbound para serem enviados para o Zabbix Server. No /etc/zabbix/zabbix_agentd.conf adicione no final dele as linhas abaixo:
UserParameter=unbound.type[*],echo -n 0; sudo /usr/sbin/unbound-control stats_noreset | grep num.query.type.$1= | cut -d= -f2 UserParameter=unbound.mem[*],sudo /usr/sbin/unbound-control stats_noreset | grep mem.$1= | cut -d= -f2 UserParameter=unbound.flag[*],sudo /usr/sbin/unbound-control stats_noreset | grep num.query.$1= | cut -d= -f2 UserParameter=unbound.total[*],sudo /usr/sbin/unbound-control stats_noreset | grep total.num.$1= | cut -d= -f2
Crie o arquivo /etc/sudoers.d/zabbix com o conteúdo abaixo:
zabbix ALL = NOPASSWD: /usr/sbin/unbound-control
Essa instrução acima dá permissão ao usuário zabbix poder rodar o comando /usr/sbin/unbound-control como root e assim poder enviar os dados para o Zabbix. Não esqueça de reiniciar o serviço do Zabbix Agent para efetivar a configuração.
Balanceando o processamento e mantendo a hora certa
Vamos instalar 2 programas agora, o IRQBalance e o Chrony. O primeiro para balancear a carga entre os cores e o segundo para manter a data e hora certas no sistema:
# apt install irqbalance # systemctl enable irqbalance # apt install chrony
Após a instalação do Chrony edite o arquivo /etc/chrony/chrony.conf, comente e a linha abaixo e adicione seus servidores NTP. Caso não tenha servidores NTP, estou colocando os do NIC.br aqui.
#pool 2.debian.pool.ntp.org iburst server a.st1.ntp.br iburst nts server b.st1.ntp.br iburst nts server c.st1.ntp.br iburst nts server d.st1.ntp.br iburst nts
# systemctl restart chronyd.service
Cheque com o chronyc se os servidores estão OK:
# chronyc sourcestats Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev ============================================================================== a.st1.ntp.br 10 5 155m -0.027 0.030 -71us 51us b.st1.ntp.br 11 7 344m +0.068 0.079 +23ms 382us c.st1.ntp.br 6 3 344m +0.026 0.037 -124us 92us 200.20.186.76 9 3 138m -0.022 0.031 +172us 42us
# chronyc sources MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^* a.st1.ntp.br 1 10 377 588 +487us[ +397us] +/- 12ms ^- b.st1.ntp.br 2 10 377 830 +23ms[ +23ms] +/- 49ms ^+ c.st1.ntp.br 2 10 21 1038 -147us[ -242us] +/- 17ms ^+ 200.20.186.76 1 10 377 1032 +381us[ +285us] +/- 15ms
Configurando o FRRouting
Nesse ponto iremos configurar o FRRouting para enviar os IPs das loopbacks e o /30 para o nosso PE do diagrama. Em /etc/frr/daemons habilite o parâmetro conforme abaixo:
ospfd=yes
Edite o arquivo /etc/frr/frr.conf e deixe com o conteúdo abaixo, para ficar conforme nosso diagrama do projeto:
frr version 8.2.2 frr defaults traditional hostname dns-recursivo-01 log syslog informational no ip forwarding no ipv6 forwarding service integrated-vtysh-config ! interface ens18 ip ospf network point-to-point exit ! router ospf ospf router-id 172.16.0.6 network 10.10.10.10/32 area 0.0.0.0 network 10.10.9.9/32 area 0.0.0.0 network 172.16.0.4/30 area 0.0.0.0 exit !
# systemctl restart frr.service
Cheque se está tudo OK com o OSPF e verifique no PE se está recebendo os prefixos anunciados.
# vtysh -c 'show ip ospf neighbor' Neighbor ID Pri State Up Time Dead Time Address Interface RXmtL RqstL DBsmL 172.16.0.5 1 Full/- 10m49s 35.310s 172.16.0.5 ens18:172.16.0.6 0 0 0
# vtysh -c 'show ip ospf neighbor detail' Neighbor 172.16.0.5, interface address 172.16.0.5 In the area 0.0.0.0 via interface ens18 Neighbor priority is 1, State is Full/-, 5 state changes Most recent state change statistics: Progressive change 21w3d15h ago DR is 0.0.0.0, BDR is 0.0.0.0 Options 18 *|-|-|EA|-|-|E|- Dead timer due in 34.685s Database Summary List 0 Link State Request List 0 Link State Retransmission List 0 Thread Inactivity Timer on Thread Database Description Retransmision off Thread Link State Request Retransmission on Thread Link State Update Retransmission on Graceful restart Helper info: Graceful Restart HELPER Status : None
Configurando o Unbound
Abaixo a configuração que usaremos nos servidores atentando para o detalhe do num-threads, esse deve ter o valor igual ao número de CPUs do servidor.
Também os IPs utilizados em outgoing-interface que serão diferentes em cada servidor, esses serão os IPs usados para recursividade. Consulte o manual do Unbound para obter mais informações sobre cada parâmetro listado na configuração.
O tuning no Unbound pode ser alterado conforme abaixo:
num-threads = nº CPUs so-reuseport = yes *-slabs = potência de 2 próximo ao num-threads msg-cache-size = 1G (quantidade de memória pra usar de cache) rrset-cache-size = 2 * msg-cache-size outgoing-range = 8192 num-queries-per-thread = 4096 so-rcvbuf e so-sndbuf = 4m ou 8m para servidores com muita requisição
Agora vamos criar nosso arquivo de configuração base:
server: verbosity: 1 statistics-interval: 0 statistics-cumulative: no extended-statistics: yes num-threads: 6 serve-expired: yes interface: 127.0.0.1 interface: 10.10.10.10 interface: 10.10.9.9 interface: 172.16.0.6 interface: ::1 interface-automatic: no outgoing-interface: 198.18.1.10 outgoing-interface: 2001:db8::faca:198:18:1:10 outgoing-range: 8192 outgoing-num-tcp: 1024 incoming-num-tcp: 2048 so-rcvbuf: 4m so-sndbuf: 4m edns-buffer-size: 1232 msg-cache-size: 1G msg-cache-slabs: 4 num-queries-per-thread: 4096 rrset-cache-size: 2G rrset-cache-slabs: 4 infra-cache-slabs: 4 do-ip4: yes do-ip6: yes do-udp: yes do-tcp: yes chroot: "" username: "unbound" directory: "/etc/unbound" logfile: "/var/log/unbound/unbound.log" use-syslog: no log-time-ascii: yes log-queries: no pidfile: "/var/run/unbound.pid" root-hints: "/etc/unbound/named.cache" hide-identity: yes hide-version: yes unwanted-reply-threshold: 10000000 prefetch: yes prefetch-key: yes rrset-roundrobin: yes minimal-responses: yes module-config: "respip validator iterator" val-clean-additional: yes val-log-level: 1 key-cache-slabs: 4 deny-any: yes access-control: 198.18.0.0/22 allow access-control: 2001:db8::/32 allow rpz: name: rpz.block.host.local.zone zonefile: /etc/unbound/rpz.block.hosts.zone rpz-action-override: nxdomain python: remote-control: control-enable: yes server-key-file: "/etc/unbound/unbound_server.key" server-cert-file: "/etc/unbound/unbound_server.pem" control-key-file: "/etc/unbound/unbound_control.key" control-cert-file: "/etc/unbound/unbound_control.pem" auth-zone: name: "." master: "b.root-servers.net" master: "c.root-servers.net" master: "d.root-servers.net" master: "f.root-servers.net" master: "g.root-servers.net" master: "k.root-servers.net" master: "lax.xfr.dns.icann.org" master: "iad.xfr.dns.icann.org" fallback-enabled: yes for-downstream: no for-upstream: yes zonefile: "/var/lib/unbound/root.zone"
No parâmetro interface colocamos os IPs que serão usados para consulta dos clientes como o 10.10.10.10 e o 10.10.9.9. Ali repare que coloquei também o IP privado 172.16.0.6, isso porque cada servidor terá o seu IP privado e este deve ser usado pelo seu sistema de monitoramento para checar cada servidor. No outgoing-interface teremos os IPs, tanto IPv4 quanto IPv6, para que seja feita a recursividade na Internet utilizando eles. Não tem IPv6 ainda na sua rede? Dê uma olhada nesse artigo. Outro parâmetro importante é o access-control e é através dele que liberamos os prefixos IP para consultarem no nosso DNS Recursivo. No exemplo estou liberando todo o prefixo 198.18.0.0/22 e o prefixo 2001:db8::/32. Além da ACL no Unbound, recomendo que crie um filtro de pacotes com iptables ou nftables protegendo seu sistema e liberando as portas 53/UDP, 53/TCP e 443/TCP apenas para seus clientes. Falarei sobre a 443/TCP mais para frente nessa mesma documentação.
Agora criaremos o arquivo RPZ (Response Policy Zones). Esse arquivo contém os sites que serão bloqueados via DNS Recursivo. São aqueles sites que às vezes você recebe um Ofício da Justiça solicitando o bloqueio deles. Não entrarei no mérito da efetividade desses bloqueios, porque muitos de vocês sabem que tecnicamente, existem formas de se fazer um bypass através desses bloqueios. Contudo vamos deixar nosso ambiente preparado para esses bloqueios e por isso crie o arquivo /etc/unbound/rpz.block.hosts.zone com esse conteúdo de exemplo:
$TTL 2h @ IN SOA localhost. root.localhost. (2 6h 1h 1w 2h) IN NS localhost. ; RPZ manual block hosts *.josedascoves.com CNAME . josedascoves.com CNAME .
No exemplo acima estamos bloqueando qualquer consulta de DNS para josedascoves.com ou qualquer coisa .josedascoves.com.
Para testar podemos fazer assim do próprio servidor:
# host josedascoves.com ::1 Using domain server: Name: ::1 Address: ::1#53 Aliases: Host josedascoves.com not found: 3(NXDOMAIN)
Se a resposta for NXDOMAIN então está funcionando o bloqueio. Para incluir novos bloqueios basta adicionar os domínios, um abaixo do outro, conforme o exemplo que coloquei no arquivo RPZ.
Acertando o resolv.conf
Vamos modificar nosso /etc/resolv.conf para utilizar DNS externo. Sim você deve estar se perguntando em qual situação isso seria utilizado. Primeiro entenda que o Unbound não irá utilizar o DNS externo para fazer as consultas na Internet e sim, qualquer teste que você faça do servidor precisará apontar para o Unbound usando os IPs 127.0.0.1 ou ::1. Faremos isso pela seguinte situação: imagine que o daemon unbound morreu mas você ainda continua com conectividade na Internet. Você conseguiria acessar qualquer local na Internet através do IP mas não através do hostname porque não conseguiria resolver nomes, seu unbound estaria fora do ar. Imagine ainda que você gostaria que seu servidor te avisasse do problema via Telegram ou e-mail. Por isso estamos utilizando um DNS externo no /etc/resolv.conf, apenas para essas situações. Se você não quiser utilizar desse recurso, pode usar o 127.0.0.1 e ::1 no lugar.
nameserver 8.8.8.8 nameserver 8.8.4.4 nameserver 2001:4860:4860::8888
Script de teste de recursividade
Estamos montando uma Rede de DNS Recursivo Anycast, então é muito importante que você monitore essa rede para saber se algum node morreu e iniciar o troubleshooting, resolver o problema e levantar o sistema novamente. Tudo isso é importante mas o cliente não deve ficar esperando até você resolver o problema, seu sistema precisa ser inteligente o suficiente para se remover da Rede quando tiver um problema e se inserir novamente, quando o problema estiver sido solucionado. Se você montar uma Rede de DNS e um dos nodes apresentar algum problema, todos os clientes atendidos por aquele node migrarão automaticamente e transparentemente para outro DNS Recursivo Anycast mais próximo. Isso se chama disponibilidade.
O script /root/scripts/checa_dns.sh abaixo tem a função de fazer os testes de recursividade e checar se o daemon do unbound continua rodando. Se algo acontecer, ele para o anúncio do 10.10.10.10 e 10.10.9.9 e retorna eles quando tudo estiver resolvido.
# mkdir /root/scripts
#!/usr/bin/env bash #Script para teste de recursividade v2.2 # Por Marcelo Gondim # Em 24-12-2022 # # checa_dns.sh is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #----------------------------------------------------------------------- #Informe um domínio por linha: dominios_testar=( www.google.com www.terra.com.br www.uol.com.br www.globo.com www.facebook.com www.youtube.com www.twitch.com www.discord.com www.debian.org www.redhat.com ) corte_taxa_falha=100 #Porcentagem de falha para executar uma ação #----------------------------------------------------------------------- remove_ospf() { habilitado="`vtysh -c 'show run' | grep \"10.10.10.10\"`" if [ "$habilitado" != "" ]; then vtysh -c 'conf t' -c 'router ospf' -c 'no network 10.10.10.10/32 area 0.0.0.0' -c 'end' -c 'wr' vtysh -c 'conf t' -c 'router ospf' -c 'no network 10.10.9.9/32 area 0.0.0.0' -c 'end' -c 'wr' #echo "Servidor $HOSTNAME morreu!" | /usr/local/sbin/telegram-notify --error --text - fi } adiciona_ospf() { habilitado="`vtysh -c 'show run' | grep \"10.10.10.10\"`" if [ "$habilitado" == "" ]; then vtysh -c 'conf t' -c 'router ospf' -c 'network 10.10.10.10/32 area 0.0.0.0' -c 'end' -c 'wr' vtysh -c 'conf t' -c 'router ospf' -c 'network 10.10.9.9/32 area 0.0.0.0' -c 'end' -c 'wr' #echo "Servidor $HOSTNAME retornou do inferno!" | /usr/local/sbin/telegram-notify --success --text - fi } systemctl status unbound &> /dev/null; if [ $? -ne 0 ]; then #echo "Servidor $HOSTNAME morreu DNS mas tentando levantar!" | /usr/local/sbin/telegram-notify --error --text - systemctl restart unbound systemctl status unbound &> /dev/null; if [ $? -ne 0 ]; then remove_ospf exit fi #echo "Servidor $HOSTNAME servico DNS voltou mas tinha morrido!" | /usr/local/sbin/telegram-notify --success --text - fi systemctl status dnsdist &> /dev/null; if [ $? -ne 0 ]; then #echo "Servidor $HOSTNAME morreu DoH mas tentando levantar!" | /usr/local/sbin/telegram-notify --error --text - systemctl restart dnsdist systemctl status dnsdist &> /dev/null; #if [ $? -eq 0 ]; then # echo "Servidor $HOSTNAME servico DoH voltou mas tinha morrido!" | /usr/local/sbin/telegram-notify --success --text - #fi fi qt_falhas=0 qt_total="${#dominios_testar[@]}" echo "total_dominios: $qt_total" for site in "${dominios_testar[@]}" do resolver="127.0.0.1" echo -e " - dominio $site - $resolver - \c" host $site $resolver &> /dev/null if [ $? -ne 0 ]; then ((qt_falhas++)) echo -e "[Falhou]" else echo -e "[OK]" fi done taxa_falha=$((qt_falhas*100/qt_total)) echo "Falhas $qt_falhas/$qt_total ($taxa_falha%)" if [ "$taxa_falha" -ge "$corte_taxa_falha" ]; then remove_ospf exit fi adiciona_ospf
Se rodarmos o script manualmente veremos isto:
# /root/scripts/checa_dns.sh total_dominios: 10 - dominio www.google.com - 127.0.0.1 - [OK] - dominio www.terra.com.br - 127.0.0.1 - [OK] - dominio www.uol.com.br - 127.0.0.1 - [OK] - dominio www.globo.com - 127.0.0.1 - [OK] - dominio www.facebook.com - 127.0.0.1 - [OK] - dominio www.youtube.com - 127.0.0.1 - [OK] - dominio www.twitch.com - 127.0.0.1 - [OK] - dominio www.discord.com - 127.0.0.1 - [OK] - dominio www.debian.org - 127.0.0.1 - [OK] - dominio www.redhat.com - 127.0.0.1 - [OK] Falhas 0/10 (0%)
Se acontecer 100% de falhas o script irá remover os anúncios do OSPF. Se o daemon do unbound morrer, ele tentará reiniciá-lo. Se tudo normalizar o script irá retornar os anúncios para o OSPF. Deixei comentado no script as partes que enviariam uma notificação para o Telegram. Existem diversas documentações sobre isso na Internet, eu mesmo tenho uma. Assim que eu publicar aqui, atualizo essa documentação e sinta-se à vontade de modificar como desejar.
# chmod 700 /root/scripts/checa_dns.sh
Adicione a linha abaixo em seu /etc/crontab:
*/1 * * * * root /root/scripts/checa_dns.sh
Habilitando o DoH (DNS over HTTPS) - opcional
Essa parte dará um pouco mais de trabalho mas é opcional. O recurso do DoH vem para trazer mais segurança e privacidade para o cliente. É um recurso muito pouco utilizado ainda mas que seu cliente pode vir a pedir algum dia. O Unbound, desde a versão 1.12.0, possui suporte ao DoH mas no caso do Debian 11, o pacote não foi compilado com a biblioteca nghttp2 e por isso não irá funcionar. Já se foi o tempo em que eu gostava de compilar programas em meus servidores, eram horas fazendo um make no código fonte. Hoje prezo bastante pelo empacotamento da distribuição e sua equipe de segurança para ajudar a manter meus sistemas seguros. Vamos utilizar para esse serviço um outro programa que irá tratar o DoH e repassar para o Unbound.
Instalando o DNSdist. Este é o programa que fará o nosso DoH. Você precisará gerar certificados SSL legítimos e para isso você poderá usar o Let's Encrypt só que de uma forma não tão convencional.
# apt install dnsdist
Na sequência vamos instalar o Let's Encrypt para gerarmos nosso certificado SSL:
# apt install letsencrypt
Escolha um hostname para ser usado no nosso DoH e aponte ele no seu DNS Autoritativo para seus IPs 10.10.10.10 e 10.10.9.9. Aqui vamos usar o seguinte como exemplo: doh.ispup.com.br. Para gerarmos nosso certificado iremos usar o tipo DNS-01, ele não necessita que tenhamos um servidor web rodando no servidor e nem tão pouco levanta um serviço na porta 80 para checar o hostname. Ele utiliza o DNS como validador e vai te solicitar que crie um registro TXT no seu DNS Autoritativo para provar que você tem o controle sobre aquele hostname.
Usaremos a seguinte instrução:
# certbot certonly --manual --preferred-challenges dns -d "doh.ispup.com.br" Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator manual, Installer None Requesting a certificate for doh.ispup.com.br Performing the following challenges: dns-01 challenge for doh.ispup.com.br - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name _acme-challenge.doh.ispup.com.br with the following value: 2xuoLXAIN4f5VT2hFaoVA2bWrH7UyOaEI9e7Mu0C0ug Before continuing, verify the record is deployed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Press Enter to Continue
Nesse momento você cria o registro TXT no seu DNS Autoritativo conforme ele solicitou: _acme-challenge.doh.ispup.com.br IN TXT "2xuoLXAIN4f5VT2hFaoVA2bWrH7UyOaEI9e7Mu0C0ug" e somente depois de criado e checado no DNS, você pressiona o Enter para continuar. Você pode checar dessa forma:
# host -t txt _acme-challenge.doh.ispup.com.br _acme-challenge.doh.ispup.com.br descriptive text "2xuoLXAIN4f5VT2hFaoVA2bWrH7UyOaEI9e7Mu0C0ug"
Se devolver corretamente o registro conforme acima, pode dar o Enter e finalizar. Para que o sistema sempre atualize o certificado de tempo em tempo adicione as instruções abaixo no /etc/crontab:
00 00 1 * * root /usr/bin/certbot -q renew; cp /etc/letsencrypt/live/doh.ispup.com.br/fullchain.pem /etc/letsencrypt/live/doh.ispup.com.br/privkey.pem /etc/dnsdist/; chown _dnsdist: /etc/dnsdist/*.pem;
Podemos observar nas instruções acima que além de renovar o certificado com o renew, copiamos ele para o dnsdist poder utilizar. Repare que você vai precisar também copiar esse certificado para seus outros servidores, escolha um servidor para manter o certificado sempre atualizado e crie um script que faça a mesma cópia remotamente para os outros servidores. O scp e o rsync são seus aliados nisso.
Configurando o DNSdist
Agora que já temos o certificado vamos deixar o /etc/dnsdist/dnsdist.conf desse jeito:
-- dnsdist configuration file, an example can be found in /usr/share/doc/dnsdist/examples/ -- disable security status polling via DNS setSecurityPollSuffix("") -- altera a porta de DNS para nao conflitar com o dns recursivo local setLocal("127.0.0.1:5353") -- Permite query das redes abaixo addACL('198.18.0.0/22') -- Habilita o DoH nos IPs abaixo na porta 443/tcp addDOHLocal("10.10.10.10:443", "/etc/dnsdist/fullchain.pem", "/etc/dnsdist/privkey.pem", { "/" }, { doTCP=true, reusePort=true, tcpFastOpenSize=0 }) addDOHLocal("10.10.9.9:443", "/etc/dnsdist/fullchain.pem", "/etc/dnsdist/privkey.pem", { "/" }, { doTCP=true, reusePort=true, tcpFastOpenSize=0 }) -- Repassa a consulta pro DNS Recursivo local newServer("127.0.0.1:53")
Essa configuração acima é bem simples:
setLocal(): trocamos a porta para 5353, para não conflitar com a porta 53 usada pelo Unbound.
addACL(): esse parâmetro diz qual prefixo pode utilizar esse serviço. Adicione quantos prefixos precisar, basta adicionar um por linha conforme fiz com o 198.18.0.0/22.
addDOHLocal(): aqui dizemos os IPs usados pelos clientes e que disponibilizamos via Anycast. No nosso caso 10.10.10.10 e 10.10.9.9 juntamente com os certificados.
newServer(): esse parâmetro repassa as consultas DoH para o nosso serviço Unbound.
Para usar o recurso do DoH você precisará habilitar o recurso no seu navegador e informar a URL. Vou colocar o exemplo do Google Chrome: clique em chrome://settings/security?search=dns e ative Usar DNS seguro, selecione Personalizado e adicione nossa URL: