Mudanças entre as edições de "DNS Recursivo Anycast Hyperlocal"

De Wiki BPF
Ir para: navegação, pesquisa
(Configurando o Unbound)
 
(40 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 1: Linha 1:
== Descrição ==
+
__TOC__
Um dos recursos extremamente importante para o acesso à Internet é o '''DNS (Domain Name System)''' e sem ele teríamos que decorar milhares de endereços IP para acessar sites e serviços na Internet. Basicamente temos 2 tipos de DNS, o recursivo e o autoritativo. O recursivo é um serviço que utilizamos para fazer consultas aos DNS(es) Autoritativos que são aqueles responsáveis pelas tabelas que associam o host name a um IP e no caso de resolução reversa, o IP ao host name. O exemplo mais comum de DNS reverso é o do Google, os IPs 8.8.8.8, 8.8.4.4, 2001:4860:4860::8888 e 2001:4860:4860::8844. Se você está estranhando os 2 últimos endereços, é porque provavelmente ainda não ouviu falar de IPv6 e isso não é bom. Procure estudar sobre isso e um bom lugar para começar é [http://ipv6.br/pagina/curso-basico-ead aqui]. O serviço de DNS deve ser levado muito a sério pelas empresas que o implementam, porque a sua falha pode literalmente parar a Internet para milhares de pessoas que confiam nele ou até pior, um DNS mal intencionado ou comprometido, pode ser usado para direcionar usuários a acessarem sites falsos. Esse artifício visa coletar as credenciais dos usuários e assim poderem aplicar golpes e outras formas danosas e prejudiciais à vítima. Ter um DNS recursivo é muito bom para o ISP, mas cuidar dele, mantê-lo atualizado e protegido, é mais importante ainda.
+
==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:
 +
[[Arquivo:Dns hierarquia.png|esquerda|commoldura]]
 +
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: '''<nowiki>https://wiki.brasilpeeringforum.org</nowiki>''',
  
Nesse artigo mostrarei a configuração de um '''DNS recursivo anycast''', diferente dos '''DNS recursivos unicast''' comumente encontrados pelos ISPs a fora. No modelo Unicast, normalmente se tem um ou mais servidores de DNS que são consultados e estão fisicamente em um local. O problema é que essa abordagem não poderia ter por exemplo, 5 servidores, e utilizar todos configurados ao mesmo tempo nos sistemas, porque muitos sistemas só aceitam até 3 servidores recursivos em suas configurações e mais que isso são ignorados. Você poderia então utilizar apenas 2 servidores, isso resolveria o problema mas eles precisariam estar em um determinado local. Mas e se lá na frente você resolver abrir seu negócio em uma outra cidade e em outro estado? Nesse caso poderia continuar usando os mesmos servidores mas aumentando um pouco a latência. Mas se acontecesse algo na interconexão desses servidores? Com o uso do DNS Anycast você poderia ter um servidor DNS recursivo em cada cidade e respondendo pelo mesmo IP. Em caso de problemas com qualquer servidor, o outro passaria a responder para todos os seus assinantes. Seria muito bom ter um cara como o "8.8.8.8" do Google, não é mesmo? Além desse recurso, usaremos o '''Hyperlocal''' que é uma técnica que faz uma cópia das tabelas dos DNS Root Servers e mantém localmente no seu servidor recursivo. Isso deixa rápida toda resposta que seria endereçada primeiramente aos DNS(es) Raiz da Internet, principalmente consultas de domínios inválidos ou digitados erradamente.
+
'''<big>1)</big>''' <big>O</big> <big>navegador irá requisitar do '''DNS Recursivo''' utilizado pelo usuário, o '''endereço IP''' que responde pelo nome '''wiki.'''</big>'''brasilpeeringforum.org'''<big>. Isso porque todos os acessos se dão na Internet através do '''endereço''' '''IP''' e não através do '''nome'''. Imaginem se tivéssemos que decorar os endereços IPs de todos os sites e serviços que quiséssemos acessar na Internet?</big>
 +
 
 +
<big>'''2)''' Nosso DNS Recursivo checa se a informação consta em seu cache.</big> Se a informação existir ela é devolvida ao navegador do usuário e aí este consegue acessar o site.
 +
 
 +
'''3)''' Do contrário o DNS Recursivo pergunta ao '''Root Server''' quem é o '''TLD (Top Level Domain)''' responsável para atender a requisição.
 +
 
 +
'''4)''' O '''Root Server''' informa ao DNS Recursivo o endereço do '''TLD responsável'''. No Brasil o '''TLD''' responsável pelo '''.br''' seria o '''Registro.br'''.
 +
 
 +
'''5)''' O DNS Recursivo pergunta ao '''TLD''' sobre '''wiki.brasilpeeringforum.org''' e este responde com os endereços IP dos '''DNS Autoritativos''' responsáveis pelo domínio '''brasilpeeringforum.org.'''
 +
 
 +
'''6)''' O DNS Recursivo pergunta aos '''DNS Autoritativos''' pelo '''wiki.brasilpeeringforum.org''' e este responde com o '''endereço IP'''.
 +
 
 +
'''7)''' Por último o DNS Recursivo devolve a informação para o navegador do usuário.
 +
 
 +
Como que se dá a comunicação entre os '''DNS(s) Recursivos, Root Servers, TLDs''' 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 nome para o endereço IP, a '''Internet''' não funcionaria e por isso precisamos cuidar muito bem desses dois serviços.
 +
<br><br><br><br>
 +
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.
 +
<br>
 +
== 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 a 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=Rsvpu5uF2Io
 +
 
 +
== 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 [https://www.debian.org/ 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 ==
 
== Diagrama ==
Para que possamos visualizar nossa configuração, abaixo temos um diagrama hipotético dos nossos servidores e da rede em si:
+
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.
[[Arquivo:Diagrama dns anycast2.png|nenhum|miniaturadaimagem|807x807px]]
+
[[Arquivo:Diagrama dns recursivo.drawio.png|esquerda|commoldura]]
Explicando o cenário: cada servidor DNS fechará uma sessão iBGP com o seu router através dos endereços /30 IPv4 e /64 IPv6 (usando IPs de documentação [https://datatracker.ietf.org/doc/html/rfc3849 RFC3849] e [https://datatracker.ietf.org/doc/html/rfc5737 RFC5737] representando seus IPs públicos). Nessa sessão BGP serão anunciados os IPs privados [https://datatracker.ietf.org/doc/html/rfc1918 RFC1918] e [https://datatracker.ietf.org/doc/html/rfc4193 RFC4193] configurados nas loopbacks que serão: 10.10.10.10 e fc00::10:10:10:10. Divulgando esses IPs privados para os assinantes, você esconde o real IP público dos servidores, evitando que alguém de fora envie ataques para seus servidores DNS.
+
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:
== Servidores utilizados ==
 
* VM com 4 cores (Intel(R) Xeon(R) Silver 4214R CPU @ 2.40GHz).
 
* 4Gb memória.
 
* 20Gb disco.
 
  
== Softwares utilizados ==
+
'''10.10.10.10/32'''
Para instalação e configuração dos Servidores DNS Recursivos utilizaremos Software Livre, o que nos permite uma economia em licenças de sistemas fechados e proprietários. Abaixo a descrição deles:
 
* [https://www.debian.org/ Debian Linux Buster (10.x).]
 
* [https://frrouting.org/ FRRouting 7.5.1.]
 
* Unbound 1.9.0 (pacote da distribuição Debian). Obs.: essa versão do Unbound já possui suporte ao Hyperlocal nativo.
 
* IRQBalance (pacote do Debian).
 
* OpenNTPd (também pacote do Debian).
 
* Shell script em bash, para checar se o DNS está OK e se não tiver retirar ele do anúncio.
 
  
== Gráfico de utilização desse sistema em produção ==
+
'''10.10.9.9/32'''
[[Arquivo:Dns Anycast Grafico.png|nenhum|miniaturadaimagem|816x816px]]
 
  
== Instalação e configuração do Servidor 1 ==
+
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'''.
Instale um sistema Debian 10 com o mínimo necessário, uma versão que costumo utilizar bastante é a que vem com firmwares e pode ser encontrado [https://cdimage.debian.org/cdimage/unofficial/non-free/cd-including-firmware/10.10.0+nonfree/amd64/iso-cd/firmware-10.10.0-amd64-netinst.iso aqui]. Atualmente está na versão 10.10, mas caso lancem uma versão mais recente, basta alterar o link. Não cobriremos a instalação do Debian, pois existem diversos vídeos no Youtube ensinando como proceder. Após a instalação faremos os ajustes na configuração para que possa então acomodar nosso serviço de DNS.
 
  
=== Instalação dos pacotes ===
+
Agora entregando '''10.10.10.10''' e '''10.10.9.9''' não teríamos problemas com a rede '''192.168.0.0/24'''.
  
==== FRR ====
+
'''Motivos para usarmos IPs privados:'''
Vamos configurar o repositório oficial do FRR e instalar os pacotes. O FRR é um fork do Quagga e será o responsável pela sessão BGP entre o servidor DNS e o router:<pre>
+
* 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.
# echo "deb https://deb.frrouting.org/frr buster frr-stable" > /etc/apt/sources.list.d/frr.list
+
* Nem mesmo o cliente da sua rede conhece os '''IPs públicos''' utilizados para recursividade na Internet.
# curl -s https://deb.frrouting.org/frr/keys.asc | apt-key add -
+
* 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.
# apt update
+
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.
# apt install frr frr-doc frr-pythontools
 
</pre>
 
  
==== Unbound ====
+
Nessa topologia usando '''Anycast''', o cliente será sempre atendido pelo '''DNS Recursivo''' mais próximo, desde que os pesos no '''OSPF''' estejam ajustados corretamente.
O Unbound é exatamente o serviço de DNS recursivo que vamos utilizar nesse artigo. Por que o Unbound? Porque ele só faz recursividade, que é o que desejamos e faz isso com uma excelente performance e segurança. Ele é desenvolvido pela [https://www.nlnetlabs.nl/ NLNet Labs]mesma empresa que desenvolve o '''Krill''' e '''Routinator''', utilizados na implantação do '''RPKI''' e o '''NSD''' (servidor DNS autoritativo) e todos sempre pensando em segurança.<pre>
+
<br><br><br><br><br><br>
# apt install unbound
+
== Dados do servidor ==
</pre>
+
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.
 +
{| class="wikitable"
 +
|+
 +
!CPU
 +
!Memória
 +
!Disco
 +
!Sistema
 +
|-
 +
|2.4Ghz 4 cores
 +
|16G DDR4
 +
|30G
 +
|Debian 11 amd64 (Bullseye)
 +
|}
  
==== IRQBalance ====
+
== Softwares utilizados ==
O IRQBalance é um programa bem conhecido por distribuir o processamento entre os cores do servidor.<pre>
+
* Debian 11 amd64 (Bullseye) instalação mínima.
# apt install irqbalance
 
# systemctl enable irqbalance
 
</pre>
 
  
==== OpenNTPd ====
+
* [https://frrouting.org/ FRRouting].
Para manter o sistema sempre com o horário correto, usaremos o OpenNTPd configurado para usar o Pool do [https://ntp.br/ Nic.BR]. <pre>
+
* Unbound.
# apt install openntpd
+
* IRQBalance.
 +
* Chrony (NTP/NTS).
 +
* Shell script em bash.
  
Deixe o arquivo /etc/openntpd/ntpd.conf conforme abaixo e reinicie o serviço openntpd:
+
== Funcionalidades que teremos ==
 +
* Sistema em Anycast.
 +
* Hyperlocal.
 +
* Controle de acesso por <abbr>ACL</abbr>.
 +
* 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.
 +
* <abbr>DoH (DNS</abbr> over HTTPS) habilitado.
  
# $OpenBSD: ntpd.conf,v 1.14 2015/07/15 20:28:37 ajacoutot Exp $
+
== Monitoramento ==
# sample ntpd configuration file, see ntpd.conf(5)
+
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:
 +
[[Arquivo:Grafana dns.png|nenhum|commoldura]]
  
# Addresses to listen on (ntpd does not listen by default)
+
== Configurando a Rede ==
#listen on *
+
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.
#listen on 127.0.0.1
 
#listen on ::1
 
  
# sync to a single server
+
Em '''/etc/network/interfaces''' deixaremos assim:
#server ntp.example.org
+
# This file describes the network interfaces available on your system
servers pool.ntp.br
+
# 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.
  
# use a random selection of NTP Pool Time Servers
+
== Configuração dos repositórios Debian ==
# see http://support.ntp.org/bin/view/Servers/NTPPoolServers
+
Deixe o arquivo '''/etc/apt/sources.list''' conforme abaixo:
#servers pool.ntp.org
+
deb <nowiki>http://security.debian.org/debian-security</nowiki> bullseye-security main contrib non-free
 +
deb <nowiki>http://deb.debian.org/debian</nowiki> bullseye main non-free contrib
 +
deb <nowiki>http://deb.debian.org/debian</nowiki> bullseye-updates main contrib non-free
 +
deb <nowiki>http://deb.debian.org/debian</nowiki> 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
  
# Choose servers announced from Debian NTP Pool
+
== Fazendo algum tuning no sistema ==
#servers 0.debian.pool.ntp.org
+
Em '''/etc/sysctl.conf''' adicionamos no final do arquivo essas instruções:
#servers 1.debian.pool.ntp.org
+
net.core.rmem_max = 2147483647
#servers 2.debian.pool.ntp.org
+
net.core.wmem_max = 2147483647
#servers 3.debian.pool.ntp.org
+
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.
  
# use a specific local timedelta sensor (radio clock, etc)
+
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.
#sensor nmea0
+
# echo nf_conntrack >> /etc/modules
 +
# modprobe nf_conntrack
 +
# sysctl -p
  
# use all detected timedelta sensors
+
== Instalando o FRRouting ==
#sensor *
+
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:
</pre>
+
# echo "deb <nowiki>https://deb.frrouting.org/frr</nowiki> bullseye frr-8" > /etc/apt/sources.list.d/frr.list
 +
# curl -s <nowiki>https://deb.frrouting.org/frr/keys.asc</nowiki> | 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
  
=== Configurando a rede ===
+
== Removendo o APPARMOR ==
'''/etc/network/interfaces:'''<pre>
+
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:
# This file describes the network interfaces available on your system
+
# mkdir -p /etc/default/grub.d
# and how to activate them. For more information, see interfaces(5).
+
# echo 'GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT apparmor=0"' | tee /etc/default/grub.d/apparmor.cfg
 +
# update-grub
 +
# reboot
  
source /etc/network/interfaces.d/*
+
== Instalando o Unbound ==
 +
Nesse momento ainda não iremos configurar o Unbound, apenas instalar o pacote e acertar o ambiente. Vamos instalar o unbound do backports porque este já possui suporte ao DoH que veremos mais à frente.
 +
# apt -t bullseye-backports install unbound dns-root-data
 +
# mkdir -p /var/log/unbound
 +
# touch /var/log/unbound/unbound.log
 +
# chown -R unbound:unbound /var/log/unbound/
 +
# systemctl restart unbound
 +
Configurando o logrotate:
 +
cat << EOF > /etc/logrotate.d/unbound
 +
/var/log/unbound/unbound.log {
 +
    rotate 5
 +
    weekly
 +
    postrotate
 +
        unbound-control log_reopen
 +
    endscript
 +
}
 +
EOF
 +
Reiniciando o serviço:
 +
# systemctl restart logrotate.service
  
# The loopback network interface
+
== Preparando o monitoramento do seu DNS Recursivo ==
auto lo
+
O monitoramento do seu DNS Recursivo é muito importante e para isso vamos usar um '''template para Zabbix''', que modifiquei juntamente com o seu shell script e que enviará os dados para o seu Zabbix server via '''zabbix-sender'''. O projeto original está aqui '''https://github.com/jeftedelima/Unbound-DNS<nowiki/>.''' O xml alterado está aqui '''https://github.com/gondimcodes/template_zabbix_dns_unbound'''. Embora seja antigo é perfeitamente importável no Zabbix 6.0, por exemplo.
iface lo inet loopback
 
  
auto lo:0
+
'''<nowiki/>'''
iface lo:0 inet static
 
      address 10.10.10.10/32
 
  
iface lo:0 inet6 static
+
Teremos um shell script que você precisará colocar no seu '''/etc/crontab'''. No exemplo abaixo assumi que o shell script está em '''/root/scripts'''. De 5 em 5 minutos os dados serão enviados para o seu Zabbix server.
       address fc00::10:10:10:10
+
*/5 * * * *    root    /root/scripts/unboundSend.sh '''IP_zabbix_server''' '''nome_do_host''' 1> /dev/null
      netmask 128
+
Na linha acima, troque o '''IP_zabbix_server''' pelo '''IP do seu servidor Zabbix''' e o '''nome_do_host''' pelo '''hostname''' '''do seu DNS Recursivo'''. Você precisará instalar o pacote '''zabbix-sender''' no seu DNS Recursivo pois ele será usado para enviar os dados para o Zabbix server.
 +
 
 +
Abaixo o '''unboundSend.sh''' também alterado com inclusão de mais dados:
 +
#!/bin/bash
 +
#       @Jefte de Lima Ferreira
 +
#      jeftedelima at gmail dot com
 +
#      CRON Example
 +
#      Contributor: Marcelo Gondim - gondim at gmail dot com
 +
#      */5  **** root sh /home/dir/unboundSend.sh 192.168.10.1 Unbound 1> /dev/null
 +
 +
if [ -z ${1} ] || [ -z ${2} ] ; then
 +
        echo "You need to specify the IP address of zabbix server and hostname of your DNS Unbound on zabbix"
 +
        echo "Usage example: ./unboundSend.sh 192.168.10.1 UnboundServer"
 +
        exit 1
 +
fi
 +
 +
# ZABBIX_SERVER IP
 +
IP_ZABBIX=$1
 +
# NAME Unbound on Zabbix
 +
NAME_HOST=$2
 +
DIR_TEMP=/var/tmp/
 +
FILE="${DIR_TEMP}dump_unbound_control_stats.txt"
 +
unbound-control stats > ${FILE}
 +
 +
TOTAL_NUM_QUERIES=$(cat ${FILE} | grep -w 'total.num.queries' | cut -d '=' -f2)
 +
TOTAL_NUM_CACHEHITS=$(cat ${FILE} | grep -w 'total.num.cachehits' | cut -d '=' -f2)
 +
TOTAL_NUM_CACHEMISS=$(cat ${FILE} | grep -w 'total.num.cachemiss' | cut -d '=' -f2)
 +
TOTAL_NUM_PREFETCH=$(cat ${FILE} | grep -w 'total.num.prefetch' | cut -d '=' -f2)
 +
TOTAL_NUM_RECURSIVEREPLIES=$(cat ${FILE} | grep -w 'total.num.recursivereplies' | cut -d '=' -f2)
 +
 +
TOTAL_REQ_MAX=$(cat ${FILE} | grep -w 'total.requestlist.max' | cut -d '=' -f2)
 +
TOTAL_REQ_AVG=$(cat ${FILE} | grep -w 'total.requestlist.avg' | cut -d '=' -f2)
 +
TOTAL_REQ_OVERWRITTEN=$(cat ${FILE} | grep -w 'total.requestlist.overwritten' | cut -d '=' -f2)
 +
TOTAL_REQ_EXCEEDED=$(cat ${FILE} | grep -w 'total.requestlist.exceeded' | cut -d '=' -f2)
 +
TOTAL_REQ_CURRENT_ALL=$(cat ${FILE} | grep -w 'total.requestlist.current.all' | cut -d '=' -f2)
 +
TOTAL_REQ_CURRENT_USER=$(cat ${FILE} | grep -w 'total.requestlist.current.user' | cut -d '=' -f2)
 +
 +
TOTAL_TCPUSAGE=$(cat ${FILE} | grep -w 'total.tcpusage' | cut -d '=' -f2)
 +
 +
NUM_QUERY_TYPE_A=$(cat ${FILE} | grep -w 'num.query.type.A' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_NS=$(cat ${FILE} | grep -w 'num.query.type.NS' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_MX=$(cat ${FILE} | grep -w 'num.query.type.MX' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_TXT=$(cat ${FILE} | grep -w 'num.query.type.TXT' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_PTR=$(cat ${FILE} | grep -w 'num.query.type.PTR' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_AAAA=$(cat ${FILE} | grep -w 'num.query.type.AAAA' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_SRV=$(cat ${FILE} | grep -w 'num.query.type.SRV' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_SOA=$(cat ${FILE} | grep -w 'num.query.type.SOA' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_HTTPS=$(cat ${FILE} | grep -w 'num.query.type.HTTPS' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_TYPE0=$(cat ${FILE} | grep -w 'num.query.type.TYPE0' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_CNAME=$(cat ${FILE} | grep -w 'num.query.type.CNAME' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_WKS=$(cat ${FILE} | grep -w 'num.query.type.WKS' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_HINFO=$(cat ${FILE} | grep -w 'num.query.type.HINFO' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_X25=$(cat ${FILE} | grep -w 'num.query.type.X25' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_NAPTR=$(cat ${FILE} | grep -w 'num.query.type.NAPTR' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_DS=$(cat ${FILE} | grep -w 'num.query.type.DS' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_DNSKEY=$(cat ${FILE} | grep -w 'num.query.type.DNSKEY' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_TLSA=$(cat ${FILE} | grep -w 'num.query.type.TLSA' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_SVCB=$(cat ${FILE} | grep -w 'num.query.type.SVCB' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_SPF=$(cat ${FILE} | grep -w 'num.query.type.SPF' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_ANY=$(cat ${FILE} | grep -w 'num.query.type.ANY' | cut -d '=' -f2)
 +
NUM_QUERY_TYPE_OTHER=$(cat ${FILE} | grep -w 'num.query.type.other' | cut -d '=' -f2)
 +
 +
NUM_ANSWER_RCODE_NOERROR=$(cat ${FILE} | grep -w 'num.answer.rcode.NOERROR' | cut -d '=' -f2)
 +
NUM_ANSWER_RCODE_NXDOMAIN=$(cat ${FILE} | grep -w 'num.answer.rcode.NXDOMAIN' | cut -d '=' -f2)
 +
NUM_ANSWER_RCODE_SERVFAIL=$(cat ${FILE} | grep -w 'num.answer.rcode.SERVFAIL' | cut -d '=' -f2)
 +
NUM_ANSWER_RCODE_REFUSED=$(cat ${FILE} | grep -w 'num.answer.rcode.REFUSED' | cut -d '=' -f2)
 +
NUM_ANSWER_RCODE_nodata=$(cat ${FILE} | grep -w 'num.answer.rcode.nodata' | cut -d '=' -f2)
 +
NUM_ANSWER_secure=$(cat ${FILE} | grep -w 'num.answer.secure' | cut -d '=' -f2)
 +
 +
#      Sending info to zabbix_server, if variables is not empty!
 +
[ -z ${TOTAL_NUM_QUERIES} ] ||  zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.queries -o ${TOTAL_NUM_QUERIES}
 +
[ -z ${TOTAL_NUM_CACHEHITS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.cachehits -o ${TOTAL_NUM_CACHEHITS}
 +
[ -z ${TOTAL_NUM_CACHEMISS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.cachemiss -o ${TOTAL_NUM_CACHEMISS}
 +
[ -z ${TOTAL_NUM_PREFETCH} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.prefetch -o ${TOTAL_NUM_PREFETCH}
 +
[ -z ${TOTAL_NUM_RECURSIVEREPLIES} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.recursivereplies -o ${TOTAL_NUM_RECURSIVEREPLIES}
 +
 +
[ -z ${TOTAL_REQ_MAX} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.max -o ${TOTAL_REQ_MAX}
 +
[ -z ${TOTAL_REQ_AVG} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.avg -o ${TOTAL_REQ_AVG}
 +
[ -z ${TOTAL_REQ_OVERWRITTEN} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.overwritten -o ${TOTAL_REQ_OVERWRITTEN}
 +
[ -z ${TOTAL_REQ_EXCEEDED} ] ||  zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.exceeded -o ${TOTAL_REQ_EXCEEDED}
 +
[ -z ${TOTAL_REQ_CURRENT_ALL} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.current.all -o ${TOTAL_REQ_CURRENT_ALL}
 +
[ -z ${TOTAL_REQ_CURRENT_USER} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.current.user -o ${TOTAL_REQ_CURRENT_USER}
 +
 +
[ -z ${TOTAL_TCPUSAGE} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.tcpusage -o ${TOTAL_TCPUSAGE}
 +
 +
[ -z ${NUM_QUERY_TYPE_A} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.a -o ${NUM_QUERY_TYPE_A}
 +
[ -z ${NUM_QUERY_TYPE_NS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.ns -o ${NUM_QUERY_TYPE_NS}
 +
[ -z ${NUM_QUERY_TYPE_MX} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.mx -o ${NUM_QUERY_TYPE_MX}
 +
[ -z ${NUM_QUERY_TYPE_TXT} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.txt -o ${NUM_QUERY_TYPE_TXT}
 +
[ -z ${NUM_QUERY_TYPE_PTR} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.ptr -o ${NUM_QUERY_TYPE_PTR}
 +
[ -z ${NUM_QUERY_TYPE_AAAA} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.aaaa -o ${NUM_QUERY_TYPE_AAAA}
 +
[ -z ${NUM_QUERY_TYPE_SRV} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.srv -o ${NUM_QUERY_TYPE_SRV}
 +
[ -z ${NUM_QUERY_TYPE_SOA} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.soa -o ${NUM_QUERY_TYPE_SOA}
 +
[ -z ${NUM_QUERY_TYPE_HTTPS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.https -o ${NUM_QUERY_TYPE_HTTPS}
 +
[ -z ${NUM_QUERY_TYPE_TYPE0} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.type0 -o ${NUM_QUERY_TYPE_TYPE0}
 +
[ -z ${NUM_QUERY_TYPE_CNAME} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.cname -o ${NUM_QUERY_TYPE_CNAME}
 +
[ -z ${NUM_QUERY_TYPE_WKS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.wks -o ${NUM_QUERY_TYPE_WKS}
 +
[ -z ${NUM_QUERY_TYPE_HINFO} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.hinfo -o ${NUM_QUERY_TYPE_HINFO}
 +
[ -z ${NUM_QUERY_TYPE_X25} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.X25 -o ${NUM_QUERY_TYPE_X25}
 +
[ -z ${NUM_QUERY_TYPE_NAPTR} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.naptr -o ${NUM_QUERY_TYPE_NAPTR}
 +
[ -z ${NUM_QUERY_TYPE_DS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.ds -o ${NUM_QUERY_TYPE_DS}
 +
[ -z ${NUM_QUERY_TYPE_DNSKEY} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.dnskey -o ${NUM_QUERY_TYPE_DNSKEY}
 +
[ -z ${NUM_QUERY_TYPE_TLSA} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.tlsa -o ${NUM_QUERY_TYPE_TLSA}
 +
[ -z ${NUM_QUERY_TYPE_SVCB} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.svcb -o ${NUM_QUERY_TYPE_SVCB}
 +
[ -z ${NUM_QUERY_TYPE_SPF} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.spf -o ${NUM_QUERY_TYPE_SPF}
 +
[ -z ${NUM_QUERY_TYPE_ANY} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.any -o ${NUM_QUERY_TYPE_ANY}
 +
[ -z ${NUM_QUERY_TYPE_OTHER} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.other -o ${NUM_QUERY_TYPE_OTHER}
 +
 +
[ -z ${NUM_ANSWER_RCODE_NOERROR} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.NOERROR -o ${NUM_ANSWER_RCODE_NOERROR}
 +
[ -z ${NUM_ANSWER_RCODE_NXDOMAIN} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.NXDOMAIN -o ${NUM_ANSWER_RCODE_NXDOMAIN}
 +
[ -z ${NUM_ANSWER_RCODE_SERVFAIL} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.SERVFAIL -o ${NUM_ANSWER_RCODE_SERVFAIL}
 +
[ -z ${NUM_ANSWER_RCODE_REFUSED} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.REFUSED -o ${NUM_ANSWER_RCODE_REFUSED}
 +
[ -z ${NUM_ANSWER_RCODE_nodata} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.nodata -o ${NUM_ANSWER_RCODE_nodata}
 +
[ -z ${NUM_ANSWER_secure} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.secure -o ${NUM_ANSWER_secure}
 +
No Zabbix será registrado dados como esses abaixo e posteriormente pode ser montado um Grafana com eles:
 +
[[Arquivo:Zabbix dns01.png|nenhum|commoldura]]
 +
[[Arquivo:Zabbix dns02.png|nenhum|commoldura]]
 +
[[Arquivo:Zabbix dns03.png|nenhum|commoldura]]
 +
[[Arquivo:Zabbix dns04.png|nenhum|commoldura]]
  
# The primary network interface
+
== Balanceando o processamento e mantendo a hora certa ==
auto ens160
+
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:
iface ens160 inet static
+
# apt install irqbalance
        address 192.0.2.2/30
+
# systemctl enable irqbalance
        gateway 192.0.2.1
+
# 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
  
iface ens160 inet6 static
+
# systemctl restart chronyd.service
        address 2001:db8::192:0:2:2
+
Cheque com o '''chronyc''' se os servidores estão OK:
        netmask 64
+
# chronyc sourcestats
        gateway 2001:db8::192:0:2:1
+
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
  
</pre>
+
# 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 FRR e a sessão BGP com o router ===
+
== Configurando o FRRouting ==
Em '''/etc/frr/daemons''' modifique o valor '''bgpd=no''' para '''bgpd=yes''', dessa forma habilitaremos o BGP no FRR. Feito isso precisaremos reiniciar o serviço frr:<pre>
+
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:
# systemctl restart frr.service
+
ospfd=yes
</pre>Nosso arquivo '''/etc/frr/frr.conf''' ficará assim:
+
Edite o arquivo '''/etc/frr/frr.conf''' e deixe com o conteúdo abaixo, para ficar conforme nosso diagrama do projeto. Apenas troque '''<SENHA>''' por uma senha para fechar o OSPF com mais segurança. Essa senha deve ser usada dos dois lados.
  frr version 7.5
+
  frr version 8.2.2
 
  frr defaults traditional
 
  frr defaults traditional
  hostname unbound2
+
  hostname dns-recursivo-01
 
  log syslog informational
 
  log syslog informational
 
  no ip forwarding
 
  no ip forwarding
Linha 128: Linha 373:
 
  service integrated-vtysh-config
 
  service integrated-vtysh-config
 
  !
 
  !
  router bgp 65000
+
  interface ens18
   bgp router-id 192.0.2.2
+
   ip ospf message-digest-key 5 md5 '''<SENHA>'''
  no bgp ebgp-requires-policy
+
   ip ospf network point-to-point
   no bgp network import-check
+
exit
  neighbor 192.0.2.1 remote-as 65000
 
  neighbor 2001:db8::192:0:2:1 remote-as 65000
 
  !
 
  address-family ipv4 unicast
 
  network 10.10.10.10/32
 
  neighbor 192.0.2.1 prefix-list BLOQUEIA-TUDO in
 
  neighbor 192.0.2.1 prefix-list RECURSIVO out
 
  exit-address-family
 
  !
 
  address-family ipv6 unicast
 
  network fc00::10:10:10:10/128
 
  neighbor 2001:db8::192:0:2:1 activate
 
  neighbor 2001:db8::192:0:2:1 prefix-list BLOQUEIA-TUDO_V6 in
 
  neighbor 2001:db8::192:0:2:1 prefix-list RECURSIVO_V6 out
 
  exit-address-family
 
 
  !
 
  !
  ip prefix-list BLOQUEIA-TUDO seq 5 deny any
+
  router ospf
ip prefix-list RECURSIVO seq 5 permit 10.10.10.10/32
+
  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
 +
  area 0 authentication message-digest
 +
exit
 
  !
 
  !
ipv6 prefix-list BLOQUEIA-TUDO_V6 seq 5 deny any
+
 
  ipv6 prefix-list RECURSIVO_V6 seq 5 permit fc00::10:10:10:10/128
+
  # systemctl restart frr.service
!
+
Cheque se está tudo OK com o OSPF e verifique no PE se está recebendo os prefixos anunciados.
line vty
+
# vtysh -c 'show ip ospf neighbor'
!
 
Após configurar a sessão BGP no lado do router, verifique se as sessões foram estabelecidas IPv4 e IPv6. No nosso servidor podemos checar assim:<pre>
 
# vtysh -c 'show bgp summary'
 
</pre>Deve exibir algo parecido assim:
 
IPv4 Unicast Summary:
 
BGP router identifier 192.0.2.2, local AS number 65000 vrf-id 0
 
BGP table version 1
 
RIB entries 1, using 192 bytes of memory
 
Peers 2, using 43 KiB of memory
 
 
   
 
   
  Neighbor        V         AS  MsgRcvd  MsgSent  TblVer  InQ OutQ  Up/Down State/PfxRcd  PfxSnt
+
  Neighbor ID    Pri State          Up Time         Dead Time Address         Interface                        RXmtL RqstL DBsmL
  192.0.2.1       4      65000    427529    407165        0    0    0 20w1d08h           0       1
+
  172.16.0.5    1 Full/-          10m49s           35.310s 172.16.0.5  ens18:172.16.0.6                  0     0     0
2001:db8::192:0:2:1 4      65000    427517    407160        0   0   0 20w1d08h NoNeg
+
 
 +
# vtysh -c 'show ip ospf neighbor detail'
 
   
 
   
Total number of neighbors 2
+
  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
 
   
 
   
IPv6 Unicast Summary:
+
    Graceful restart Helper info:
BGP router identifier 192.0.2.2, local AS number 65000 vrf-id 0
+
      Graceful Restart HELPER Status : None
BGP table version 1
+
 
RIB entries 1, using 192 bytes of memory
+
== Configurando o Unbound ==
Peers 1, using 21 KiB of memory
+
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.
 
Neighbor        V        AS  MsgRcvd  MsgSent  TblVer  InQ OutQ  Up/Down State/PfxRcd  PfxSnt
 
2001:db8::192:0:2:1 4      65000    427517    407160        0    0    0 20w1d08h            0        1
 
 
Total number of neighbors 1
 
Podemos observar que não estamos recebendo nenhum prefixo do router e estamos apenas anunciando 1 prefixo IPv4 e 1 IPv6. Vejamos quais são:
 
# vtysh -c 'show ip bgp neighbors 192.0.2.1 advertised-routes'
 
BGP table version is 1, local router ID is 192.0.2.2, vrf id 0
 
Default local pref 100, local AS 65000
 
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
 
                i internal, r RIB-failure, S Stale, R Removed
 
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
 
Origin codes:  i - IGP, e - EGP, ? - incomplete
 
 
    Network          Next Hop            Metric LocPrf Weight Path
 
*> 10.10.10.10/32  0.0.0.0                  0    100  32768 i
 
 
Total number of prefixes 1
 
  
# vtysh -c 'show ip bgp ipv6 neighbors 2001:db8::192:0:2:1 advertised-routes'
+
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.
BGP table version is 1, local router ID is 192.0.2.2, vrf id 0
 
Default local pref 100, local AS 65000
 
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
 
                i internal, r RIB-failure, S Stale, R Removed
 
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
 
Origin codes:  i - IGP, e - EGP, ? - incomplete
 
 
    Network          Next Hop            Metric LocPrf Weight Path
 
*> fc00::10:10:10:10/128
 
                    ::                      0    100  32768 i
 
 
Total number of prefixes 1
 
Podemos observar que nossos IPs já estão sendo anunciados para o router e vocês devem checar se estão sendo recebidos pelo router. Não estamos entrando em detalhes na parte do router pois este pode ser de qualquer fabricante como por exemplo: Cisco, Huawei, Juniper, Mikrotik, outro FRR, etc.
 
  
=== Configurando o Unbound ===
+
O tuning no Unbound pode ser alterado conforme abaixo:
Não entraremos em detalhes sobre cada parâmetro da configuração do Unbound, porque isso fugiria bastante do objetivo deste artigo. Ao invés disso, criaremos o arquivo '''/etc/unbound/unbound.conf.d/local.conf''' e dentro dele teremos o seguinte:
+
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 em '''/etc/unbound/unbound.conf.d/local.conf''':
 
  server:
 
  server:
 
         verbosity: 1
 
         verbosity: 1
Linha 218: Linha 436:
 
         extended-statistics: yes
 
         extended-statistics: yes
 
         num-threads: 4
 
         num-threads: 4
 +
        serve-expired: yes
 
         interface: 127.0.0.1
 
         interface: 127.0.0.1
 
         interface: 10.10.10.10
 
         interface: 10.10.10.10
         interface: fc00::10:10:10:10
+
         interface: 10.10.9.9
         interface: ::1  
+
        interface: 172.16.0.6
 +
         interface: ::1
 
         interface-automatic: no
 
         interface-automatic: no
         outgoing-interface: 192.0.2.2
+
         outgoing-interface: 198.18.1.10
         outgoing-interface: 2001:db8::192:0:2:2
+
         outgoing-interface: 2001:db8::faca:198:18:1:10
 
         outgoing-range: 8192
 
         outgoing-range: 8192
         outgoing-num-tcp: 20
+
         outgoing-num-tcp: 1024
         incoming-num-tcp: 20
+
         incoming-num-tcp: 2048
 
         so-rcvbuf: 4m
 
         so-rcvbuf: 4m
 
         so-sndbuf: 4m
 
         so-sndbuf: 4m
 +
        so-reuseport: yes
 
         edns-buffer-size: 1232
 
         edns-buffer-size: 1232
         msg-cache-size: 100m
+
         msg-cache-size: 1g
 
         msg-cache-slabs: 4
 
         msg-cache-slabs: 4
         num-queries-per-thread: 8192
+
         num-queries-per-thread: 4096
         rrset-cache-size: 200m
+
         rrset-cache-size: 2g
 
         rrset-cache-slabs: 4
 
         rrset-cache-slabs: 4
 
         infra-cache-slabs: 4
 
         infra-cache-slabs: 4
Linha 241: Linha 462:
 
         do-udp: yes
 
         do-udp: yes
 
         do-tcp: yes
 
         do-tcp: yes
        access-control: 203.0.113.0/24 allow
 
        access-control: 2001:db8:1111::/48 allow
 
 
         chroot: ""
 
         chroot: ""
 
         username: "unbound"
 
         username: "unbound"
Linha 251: Linha 470:
 
         log-queries: no
 
         log-queries: no
 
         pidfile: "/var/run/unbound.pid"
 
         pidfile: "/var/run/unbound.pid"
         root-hints: "/etc/unbound/named.cache"
+
         root-hints: "/usr/share/dns/root.hints"
 
         hide-identity: yes
 
         hide-identity: yes
 
         hide-version: yes
 
         hide-version: yes
Linha 259: Linha 478:
 
         rrset-roundrobin: yes
 
         rrset-roundrobin: yes
 
         minimal-responses: yes
 
         minimal-responses: yes
 +
        module-config: "respip validator iterator"
 
         val-clean-additional: yes
 
         val-clean-additional: yes
 
         val-log-level: 1
 
         val-log-level: 1
 
         key-cache-slabs: 4
 
         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:
 
  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:
 
  auth-zone:
 
     name: "."
 
     name: "."
    master: "a.root-servers.net"
 
 
     master: "b.root-servers.net"
 
     master: "b.root-servers.net"
 
     master: "c.root-servers.net"
 
     master: "c.root-servers.net"
 
     master: "d.root-servers.net"
 
     master: "d.root-servers.net"
    master: "e.root-servers.net"
 
 
     master: "f.root-servers.net"
 
     master: "f.root-servers.net"
 
     master: "g.root-servers.net"
 
     master: "g.root-servers.net"
    master: "h.root-servers.net"
 
    master: "i.root-servers.net"
 
    master: "j.root-servers.net"
 
 
     master: "k.root-servers.net"
 
     master: "k.root-servers.net"
     master: "l.root-servers.net"
+
     master: "lax.xfr.dns.icann.org"
     master: "m.root-servers.net"
+
     master: "iad.xfr.dns.icann.org"
 
     fallback-enabled: yes
 
     fallback-enabled: yes
 
     for-downstream: no
 
     for-downstream: no
 
     for-upstream: yes
 
     for-upstream: yes
 
     zonefile: "/var/lib/unbound/root.zone"
 
     zonefile: "/var/lib/unbound/root.zone"
Os parâmetros relevantes acima são:
+
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.
Como nosso servidor possui 4 cores, colocamos 4 em num-threads também:
+
 
        num-threads: 4
+
Agora criaremos o arquivo '''RPZ''' ('''Response Policy Zones'''). Esse arquivo contém os sites que serão bloqueados via '''<abbr>DNS</abbr> 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
O interface indica quais IPs ficarão escutando o serviço na porta 53/udp/tcp para serem consultados pelo servidor e assinantes:
+
@ IN SOA localhost. root.localhost. (2 6h 1h 1w 2h)
        interface: 127.0.0.1
+
  IN NS  localhost.
        interface: 10.10.10.10
+
; RPZ manual block hosts
        interface: fc00::10:10:10:10
+
*.josedascoves.com CNAME .
        interface: ::1
+
  josedascoves.com CNAME .
+
No exemplo acima estamos bloqueando qualquer consulta de DNS para '''josedascoves.com''' ou qualquer coisa '''.josedascoves.com'''.
O outgoing-interface indica por quais IPs serão feitas as consultas de DNS para o mundo:
 
        outgoing-interface: 192.0.2.2
 
        outgoing-interface: 2001:db8::192:0:2:2
 
 
Habilita consultas IPv4 e IPv6, udp e tcp:
 
        do-ip4: yes
 
        do-ip6: yes
 
        do-udp: yes
 
        do-tcp: yes
 
 
Libera a consulta DNS para origem desses prefixos, troque pelos prefixos reais do seu ASN:
 
        access-control: 203.0.113.0/24 allow
 
        access-control: 2001:db8:1111::/48 allow
 
 
Esse trecho abaixo é referente à configuração do Hyperlocal:
 
 
auth-zone:
 
    name: "."
 
    master: "a.root-servers.net"
 
    master: "b.root-servers.net"
 
    master: "c.root-servers.net"
 
    master: "d.root-servers.net"
 
    master: "e.root-servers.net"
 
    master: "f.root-servers.net"
 
    master: "g.root-servers.net"
 
    master: "h.root-servers.net"
 
    master: "i.root-servers.net"
 
    master: "j.root-servers.net"
 
    master: "k.root-servers.net"
 
    master: "l.root-servers.net"
 
    master: "m.root-servers.net"
 
    fallback-enabled: yes
 
    for-downstream: no
 
    for-upstream: yes
 
    zonefile: "/var/lib/unbound/root.zone"
 
   
 
Após a configuração do Unbound basta reiniciar o serviço unbound e verificar se o arquivo '''/var/lib/unbound/root.zone''' foi criado com o conteúdo dos Root Servers.
 
  
Vamos alterar o '''/etc/resolv.conf''' para acertar as consultas locais e diminuir o timeout que precisaremos mais a frente no script que fará o teste se o DNS continua funcionando e em caso negativo, retirar o anúncio dos IPs:<pre>
+
Para testar podemos fazer assim do próprio servidor:
nameserver 127.0.0.1
+
  # host josedascoves.com ::1
nameserver ::1
 
options timeout:1 attempts:1
 
</pre>Feito isso teste se seu servidor está resolvendo os hosts na Internet por exemplo:
 
# host www.uol.com.br 127.0.0.1
 
Using domain server:
 
Name: 127.0.0.1
 
Address: 127.0.0.1#53
 
Aliases:
 
 
www.uol.com.br is an alias for dftex7xfha8fh.cloudfront.net.
 
dftex7xfha8fh.cloudfront.net has address 99.84.22.115
 
dftex7xfha8fh.cloudfront.net has address 99.84.22.71
 
dftex7xfha8fh.cloudfront.net has address 99.84.22.8
 
dftex7xfha8fh.cloudfront.net has address 99.84.22.27
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:6200:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:f000:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:f400:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:7800:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:ac00:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:8a00:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:f200:1:5a19:8b40:93a1
 
dftex7xfha8fh.cloudfront.net has IPv6 address 2600:9000:21ed:fc00:1:5a19:8b40:93a1
 
 
  # host www.terra.com.br ::1
 
 
  Using domain server:
 
  Using domain server:
  Name: ::1
+
  Name: ::1
  Address: ::1#53
+
  Address: ::1#53
  Aliases:  
+
  Aliases:
 
   
 
   
  www.terra.com.br is an alias for www.terra.com.br.edgesuite.net.
+
  Host josedascoves.com not found: 3(NXDOMAIN)
www.terra.com.br.edgesuite.net is an alias for a1799.dscb.akamai.net.
+
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.
a1799.dscb.akamai.net has address 23.73.211.226
 
a1799.dscb.akamai.net has address 23.73.211.186
 
a1799.dscb.akamai.net has IPv6 address 2600:1419:d400::1749:d78a
 
a1799.dscb.akamai.net has IPv6 address 2600:1419:d400::1749:d7d9
 
Ok funcionando e resolvendo tanto via IPv4 quanto via IPv6. Caso queira testar localmente com os IPs '''10.10.10.10''' e '''fc00::10:10:10:10''' você terá que adicioná-los no nosso '''local.conf''' do Unbound no parâmetro '''access-control''' liberando o '''10.10.10.10/32''' e '''fc00::10:10:10:10/128'''. Você também pode usar o programa '''dig''' para testar seu DNS recursivo:
 
# apt install bind9-dnsutils
 
  
# dig @127.0.0.1 www.terra.com.br
+
== 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.
; <<>> DiG 9.16.15-Debian <<>> @127.0.0.1 www.terra.com.br
+
  nameserver 8.8.8.8
; (1 server found)
+
  nameserver 8.8.4.4
;; global options: +cmd
+
  nameserver 2001:4860:4860::8888
;; Got answer:
 
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33258
 
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
 
 
;; OPT PSEUDOSECTION:
 
; EDNS: version: 0, flags:; udp: 1232
 
;; QUESTION SECTION:
 
;www.terra.com.br.             IN      A
 
 
;; ANSWER SECTION:
 
www.terra.com.br.       58      IN      CNAME  www.terra.com.br.edgesuite.net.
 
www.terra.com.br.edgesuite.net. 21512 IN CNAME  a1799.dscb.akamai.net.
 
a1799.dscb.akamai.net.  18      IN      A      23.196.224.219
 
  a1799.dscb.akamai.net.  18      IN      A      23.196.225.9
 
 
;; Query time: 0 msec
 
  ;; SERVER: 127.0.0.1#53(127.0.0.1)
 
  ;; WHEN: qua jun 30 10:09:19 -03 2021
 
;; MSG SIZE  rcvd: 153
 
  
# dig @::1 www.terra.com.br
+
== 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'''.
; <<>> DiG 9.16.15-Debian <<>> @::1 www.terra.com.br
 
; (1 server found)
 
;; global options: +cmd
 
;; Got answer:
 
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63261
 
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
 
 
;; OPT PSEUDOSECTION:
 
; EDNS: version: 0, flags:; udp: 1232
 
;; QUESTION SECTION:
 
;www.terra.com.br.              IN      A
 
 
;; ANSWER SECTION:
 
www.terra.com.br.      17      IN      CNAME  www.terra.com.br.edgesuite.net.
 
www.terra.com.br.edgesuite.net. 21471 IN CNAME  a1799.dscb.akamai.net.
 
a1799.dscb.akamai.net.  17      IN      A      23.196.225.9
 
a1799.dscb.akamai.net.  17      IN      A      23.196.224.219
 
 
;; Query time: 0 msec
 
;; SERVER: ::1#53(::1)
 
;; WHEN: qua jun 30 10:10:00 -03 2021
 
;; MSG SIZE  rcvd: 153
 
 
Vamos testar se nosso Hyperlocal está funcionando? Vamos fazer uma consulta de um host com nome absurdo e que não existe na Internet, sem o Hyperlocal habilitado:
 
# dig @127.0.0.1 wws.sssss.ssss
 
 
; <<>> DiG 9.16.15-Debian <<>> @127.0.0.1 wws.sssss.ssss
 
; (1 server found)
 
;; global options: +cmd
 
;; Got answer:
 
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 51541
 
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
 
 
;; OPT PSEUDOSECTION:
 
; EDNS: version: 0, flags:; udp: 1232
 
;; QUESTION SECTION:
 
;wws.sssss.ssss.                        IN      A
 
 
;; AUTHORITY SECTION:
 
.                      3600    IN      SOA    a.root-servers.net. nstld.verisign-grs.com. 2021063000 1800 900 604800 86400
 
 
;; Query time: 312 msec
 
;; SERVER: 127.0.0.1#53(127.0.0.1)
 
;; WHEN: qua jun 30 10:49:24 -03 2021
 
;; MSG SIZE  rcvd: 118
 
 
Repare que a resposta do Root Server levou 312 msec. Agora vamos fazer a mesma consulta com o Hyperlocal habilitado e unbound reiniciado:
 
# dig @127.0.0.1 wws.sssss.ssss
 
 
; <<>> DiG 9.16.15-Debian <<>> @127.0.0.1 wws.sssss.ssss
 
; (1 server found)
 
;; global options: +cmd
 
;; Got answer:
 
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48429
 
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
 
 
;; OPT PSEUDOSECTION:
 
; EDNS: version: 0, flags:; udp: 1232
 
;; QUESTION SECTION:
 
;wws.sssss.ssss.                        IN      A
 
 
;; AUTHORITY SECTION:
 
.                      86397  IN      SOA    a.root-servers.net. nstld.verisign-grs.com. 2021063000 1800 900 604800 86400
 
 
;; Query time: 0 msec
 
;; SERVER: 127.0.0.1#53(127.0.0.1)
 
;; WHEN: qua jun 30 10:52:21 -03 2021
 
;; MSG SIZE  rcvd: 118
 
Com as tabelas locais, a mesma consulta levou 0 msec. Muito bom não é mesmo?
 
 
 
Próximo passo é configurar os IPs '''10.10.10.10''' e '''fc00::10:10:10:10''' em algum dispositivo ou estação da rede e testar se está funcionando a resolução de nomes. Se chegamos até aqui com tudo funcionando então perfeito.
 
  
Vamos configurar agora um script que vai checar se o nosso DNS parou de resolver nomes e se isso ocorrer, ele deixará de anunciar os IPs de DNS para o router. Nesse caso o outro DNS continuará respondendo com os mesmos IPs.
+
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
  
=== Script do Servidor 1 ===
 
'''/root/teste_dns.sh'''
 
 
  #!/usr/bin/env bash
 
  #!/usr/bin/env bash
  <nowiki>#</nowiki>Script para teste de DNS v1.2
+
  #Script para teste de recursividade v2.2
  <nowiki>#</nowiki>-----------------------------------------------------------------------
+
  # Por Marcelo Gondim
  <nowiki>#</nowiki>Informe um domínio por linha:
+
# 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=(
 
  dominios_testar=(
 
  www.google.com
 
  www.google.com
Linha 499: Linha 573:
 
  )
 
  )
 
  corte_taxa_falha=100 #Porcentagem de falha para executar uma ação
 
  corte_taxa_falha=100 #Porcentagem de falha para executar uma ação
  <nowiki>#</nowiki>-----------------------------------------------------------------------
+
  #-----------------------------------------------------------------------
 +
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
 +
 
  qt_falhas=0
 
  qt_falhas=0
 
  qt_total="${#dominios_testar[@]}"
 
  qt_total="${#dominios_testar[@]}"
Linha 505: Linha 609:
 
  for site in "${dominios_testar[@]}"
 
  for site in "${dominios_testar[@]}"
 
  do
 
  do
<nowiki> </nowiki> resp=<nowiki>''</nowiki>
+
  resolver="127.0.0.1"
<nowiki> </nowiki> resolver="127.0.0.1"
+
  echo -e " - dominio $site - $resolver - \c"
<nowiki> </nowiki> echo " - dominio $site - $resolver"
+
  host $site $resolver &> /dev/null
<nowiki> </nowiki> resp=$( host $site $resolver | grep "connection timed out" )
+
  if [ $? -ne 0 ]; then
<nowiki> </nowiki> if [ ! -z "$resp" ]; then
+
      ((qt_falhas++))
<nowiki> </nowiki>    ((qt_falhas++))
+
      echo -e "[Falhou]"
<nowiki> </nowiki>   echo "[$resp]"
+
   else
<nowiki> </nowiki> fi
+
      echo -e "[OK]"
 +
  fi
 
  done
 
  done
 
   
 
   
Linha 519: Linha 624:
 
   
 
   
 
  if [ "$taxa_falha" -ge "$corte_taxa_falha" ]; then
 
  if [ "$taxa_falha" -ge "$corte_taxa_falha" ]; then
<nowiki> </nowiki>  habilitado="`vtysh -c 'show run' | grep \"neighbor 192.0.2.1 prefix-list BLOQUEIA-TUDO out\"`"
+
     remove_ospf
<nowiki> </nowiki>  if [ "$habilitado" == "" ]; then
+
     exit
<nowiki> </nowiki>    vtysh -c 'conf t' -c 'router bgp 65000' -c 'address-family ipv4 unicast' -c 'no neighbor 192.0.2.1 prefix-list RECURSIVO out' -c 'neighbor 192.0.2.1 prefix-list BLOQUEIA-TUDO out' -c 'end' -c 'wr'
 
<nowiki> </nowiki>     vtysh -c 'conf t' -c 'router bgp 65000' -c 'address-family ipv6 unicast' -c 'no neighbor 2001:db8::192:0:2:1 prefix-list RECURSIVO_V6 out' -c 'neighbor 2001:db8::192:0:2:1 prefix-list BLOQUEIA-TUDO out' -c 'end' -c 'wr'
 
<nowiki> </nowiki>     echo "caiu: `date`" >> /root/dnsreport.log
 
<nowiki> </nowiki>  fi
 
<nowiki> </nowiki>  exit
 
fi
 
 
habilitado="`vtysh -c 'show run' | grep \"neighbor 192.0.2.1 prefix-list RECURSIVO out\"`"
 
if [ "$habilitado" == "" ]; then
 
<nowiki> </nowiki>  vtysh -c 'conf t' -c 'router bgp 65000' -c 'address-family ipv4 unicast' -c 'no neighbor 192.0.2.1 prefix-list BLOQUEIA-TUDO out' -c 'neighbor 192.0.2.1 prefix-list RECURSIVO out' -c 'end' -c 'wr'
 
<nowiki> </nowiki>  vtysh -c 'conf t' -c 'router bgp 65000' -c 'address-family ipv6 unicast' -c 'no neighbor 2001:db8::192:0:2:1 prefix-list BLOQUEIA-TUDO out' -c 'neighbor 2001:db8::192:0:2:1 prefix-list RECURSIVO_V6 out' -c 'end' -c 'wr'
 
<nowiki> </nowiki>  echo "voltou: `date`" >> /root/dnsreport.log
 
 
  fi
 
  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
  
Sim. Para entender o que está escrito acima você precisa conhecer um pouco de linguagem de shell script em bash. Fique à vontade em melhorar ou até mesmo criar seu próprio script em outra linguagem. O conceito é bem simples e usado apenas para testar se nosso DNS está resolvendo pra fora. Porque você pode não estar conseguindo, por algum motivo, consultar na Internet pelas portas '''53/udp''' e/ou '''53/tcp''' e que poderia ser causado por algum bloqueio ou problema de fato na rede. Caso is aconteça usaremos os comandos '''vtysh''' via shell, para alterar a nossa configuração no FRR e bloquear os anúncios. Uma outra coisa que fica como dever de casa: no script ele gera um relatório de quando tem problema no DNS e quando volta a funcionar. Você pode pesquisar e utilizar os pacotes do Debian '''msmtp''' e '''msmtp-mta''', configurar ele e o script para enviar um e-mail em caso de problemas. Como não sou mau, você pode achar informação sobre isso, nos slides de uma palestra que apresentei no '''[https://www.youtube.com/watch?v=Wz2IAg6MMlU FiqueEmCasaUseDebian]''' bem [https://debianbrasil.gitlab.io/FiqueEmCasaUseDebian/arquivos/2020-05-29-sysadmin-apps-ferramentas-uteis-para-sysadmins.pdf aqui].
+
== Habilitando o DoH (<abbr>DNS</abbr> over HTTPS) - opcional ==
 +
Para habilitar o '''DoH''' no Unbound é bem simples mas só é possível com a versão '''1.17.1''' que vem no '''bullseye-backports''' e que virá na próxima versão do '''Debian 12 (Bookworm)'''. O recurso do '''DoH''' vem para trazer mais segurança e privacidade para o usuário. É um recurso muito pouco utilizado ainda mas que seu cliente pode vir a pedir algum dia.
  
Vamos agora colocar o script para rodar no cron adicionando a seguinte linha em /etc/crontab:<pre>
+
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.
*/1 *  * * *  root    /root/teste_dns.sh
 
</pre>Terminamos aqui o Servidor 1 de DNS Anycast com Hyperlocal. Para configurar o servidor 2, basta fazer os mesmos procedimentos usados no servidor 1 mas mudando os dados de conexão. Vou colocar abaixo apenas as configurações que serão diferentes.
 
  
== Instalação e configuração do Servidor 2 - apenas as diferenças ==
+
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.brasilpeeringforum.org'''. 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 '''CNAME''' no seu '''DNS Autoritativo''' para provar que você tem o controle sobre aquele hostname. Antes disso vamos instalar um programa em Python para podermos automatizar nossa renovação de certificado no futuro. Esse programa se encontra '''[https://github.com/joohoi/acme-dns-certbot-joohoi/raw/master/acme-dns-auth.py aqui]''' mas vou deixá-lo abaixo já modificado o interpretador.
  
=== Configurando a rede ===
+
Crie o arquivo '''/etc/letsencrypt/acme-dns-auth.py''' com o conteúdo abaixo:
'''/etc/network/interfaces:'''
+
#!/usr/bin/env python3
  # This file describes the network interfaces available on your system
+
import json
# and how to activate them. For more information, see interfaces(5).
+
import os
 +
import requests
 +
import sys
 +
 +
### EDIT THESE: Configuration values ###
 +
 +
# URL to acme-dns instance
 +
ACMEDNS_URL = "<nowiki>https://auth.acme-dns.io</nowiki>"
 +
# Path for acme-dns credential storage
 +
STORAGE_PATH = "/etc/letsencrypt/acmedns.json"
 +
# Whitelist for address ranges to allow the updates from
 +
# Example: ALLOW_FROM = ["192.168.10.0/24", "::1/128"]
 +
ALLOW_FROM = []
 +
# Force re-registration. Overwrites the already existing acme-dns accounts.
 +
FORCE_REGISTER = False
 +
 +
###  DO NOT EDIT BELOW THIS POINT  ###
 +
###        HERE BE DRAGONS          ###
 +
 +
DOMAIN = os.environ["CERTBOT_DOMAIN"]
 +
if DOMAIN.startswith("*."):
 +
    DOMAIN = DOMAIN[2:]
 +
VALIDATION_DOMAIN = "_acme-challenge."+DOMAIN
 +
VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"]
 +
 +
 +
class AcmeDnsClient(object):
 +
    """
 +
    Handles the communication with ACME-DNS API
 +
    """
 +
 +
    def __init__(self, acmedns_url):
 +
        self.acmedns_url = acmedns_url
 +
 +
    def register_account(self, allowfrom):
 +
        """Registers a new ACME-DNS account"""
 +
 +
        if allowfrom:
 +
            # Include whitelisted networks to the registration call
 +
            reg_data = {"allowfrom": allowfrom}
 +
            res = requests.post(self.acmedns_url+"/register",
 +
                                data=json.dumps(reg_data))
 +
        else:
 +
            res = requests.post(self.acmedns_url+"/register")
 +
        if res.status_code == 201:
 +
            # The request was successful
 +
            return res.json()
 +
        else:
 +
            # Encountered an error
 +
            msg = ("Encountered an error while trying to register a new acme-dns "
 +
                    "account. HTTP status {}, Response body: {}")
 +
            print(msg.format(res.status_code, res.text))
 +
            sys.exit(1)
 +
 +
    def update_txt_record(self, account, txt):
 +
        """Updates the TXT challenge record to ACME-DNS subdomain."""
 +
        update = {"subdomain": account['subdomain'], "txt": txt}
 +
        headers = {"X-Api-User": account['username'],
 +
                    "X-Api-Key": account['password'],
 +
                    "Content-Type": "application/json"}
 +
        res = requests.post(self.acmedns_url+"/update",
 +
                            headers=headers,
 +
                            data=json.dumps(update))
 +
        if res.status_code == 200:
 +
            # Successful update
 +
            return
 +
        else:
 +
            msg = ("Encountered an error while trying to update TXT record in "
 +
                    "acme-dns. \n"
 +
                    "------- Request headers:\n{}\n"
 +
                    "------- Request body:\n{}\n"
 +
                    "------- Response HTTP status: {}\n"
 +
                    "------- Response body: {}")
 +
            s_headers = json.dumps(headers, indent=2, sort_keys=True)
 +
            s_update = json.dumps(update, indent=2, sort_keys=True)
 +
            s_body = json.dumps(res.json(), indent=2, sort_keys=True)
 +
            print(msg.format(s_headers, s_update, res.status_code, s_body))
 +
            sys.exit(1)
 +
 +
class Storage(object):
 +
    def __init__(self, storagepath):
 +
        self.storagepath = storagepath
 +
        self._data = self.load()
 +
 +
    def load(self):
 +
        """Reads the storage content from the disk to a dict structure"""
 +
        data = dict()
 +
        filedata = ""
 +
        try:
 +
            with open(self.storagepath, 'r') as fh:
 +
                filedata = fh.read()
 +
        except IOError as e:
 +
            if os.path.isfile(self.storagepath):
 +
                # Only error out if file exists, but cannot be read
 +
                print("ERROR: Storage file exists but cannot be read")
 +
                sys.exit(1)
 +
        try:
 +
            data = json.loads(filedata)
 +
        except ValueError:
 +
            if len(filedata) > 0:
 +
                # Storage file is corrupted
 +
                print("ERROR: Storage JSON is corrupted")
 +
                sys.exit(1)
 +
        return data
 +
 +
    def save(self):
 +
        """Saves the storage content to disk"""
 +
        serialized = json.dumps(self._data)
 +
        try:
 +
            with os.fdopen(os.open(self.storagepath,
 +
                                    os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
 +
                fh.truncate()
 +
                fh.write(serialized)
 +
        except IOError as e:
 +
            print("ERROR: Could not write storage file.")
 +
            sys.exit(1)
 +
   
 +
    def put(self, key, value):
 +
        """Puts the configuration value to storage and sanitize it"""
 +
        # If wildcard domain, remove the wildcard part as this will use the
 +
        # same validation record name as the base domain
 +
        if key.startswith("*."):
 +
            key = key[2:]
 +
        self._data[key] = value
 +
 +
    def fetch(self, key):
 +
        """Gets configuration value from storage"""
 +
        try:
 +
            return self._data[key]
 +
        except KeyError:
 +
            return None
 
   
 
   
  source /etc/network/interfaces.d/*
+
  if __name__ == "__main__":
 +
    # Init
 +
    client = AcmeDnsClient(ACMEDNS_URL)
 +
    storage = Storage(STORAGE_PATH)
 
   
 
   
# The loopback network interface
+
    # Check if an account already exists in storage
auto lo
+
    account = storage.fetch(DOMAIN)
iface lo inet loopback
+
    if FORCE_REGISTER or not account:
 +
        # Create and save the new account
 +
        account = client.register_account(ALLOW_FROM)
 +
        storage.put(DOMAIN, account)
 +
        storage.save()
 
   
 
   
auto lo:0
+
        # Display the notification for the user to update the main zone
iface lo:0 inet static
+
        msg = "Please add the following CNAME record to your main DNS zone:\n{}"
      address 10.10.10.10/32
+
        cname = "{} CNAME {}.".format(VALIDATION_DOMAIN, account["fulldomain"])
 +
        print(msg.format(cname))
 
   
 
   
  iface lo:0 inet6 static
+
    # Update the TXT record in acme-dns instance
      address fc00::10:10:10:10
+
    client.update_txt_record(account, VALIDATION_TOKEN)
      netmask 128
+
 
 +
  # chmod +x /etc/letsencrypt/acme-dns-auth.py
 +
Usaremos a seguinte instrução para criar nosso certificado:
 +
# certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py --preferred-challenges dns --debug-challenges -d doh.brasilpeeringforum.org
 +
Saving debug log to /var/log/letsencrypt/letsencrypt.log
 +
Plugins selected: Authenticator manual, Installer None
 +
Cert is due for renewal, auto-renewing...
 +
Renewing an existing certificate for doh.brasilpeeringforum.org
 +
Performing the following challenges:
 +
dns-01 challenge for doh.brasilpeeringforum.org
 +
Running manual-auth-hook command: /etc/letsencrypt/acme-dns-auth.py
 +
Output from manual-auth-hook command acme-dns-auth.py:
 +
Please add the following CNAME record to your main DNS zone:
 +
_acme-challenge.doh.brasilpeeringforum.org CNAME b555d682-7b50-45d9-a92f-3c3d187dd4e7.auth.acme-dns.io.
 
   
 
   
  # The primary network interface
+
  Waiting for verification...
auto ens160
 
iface ens160 inet static
 
        address 192.0.2.6/30
 
        gateway 192.0.2.5
 
 
   
 
   
  iface ens160 inet6 static
+
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        address 2001:db8:aaaa::192:0:2:6
+
Challenges loaded. Press continue to submit to CA. Pass "-v" for more info about
        netmask 64
+
challenges.
        gateway 2001:db8:aaaa::192:0:2:5
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  [[Categoria:Infraestrutura]]
+
Press Enter to Continue
 +
Nesse momento você cria o registro '''CNAME''' no seu DNS Autoritativo conforme ele solicitou: '''_acme-challenge.doh.brasilpeeringforum.org IN CNAME b555d682-7b50-45d9-a92f-3c3d187dd4e7.auth.acme-dns.io.''' e somente depois de criado e checado no DNS, você pressiona o '''Enter''' para continuar. Você pode checar dessa forma:
 +
# host -t cname _acme-challenge.doh.brasilpeeringforum.org
 +
_acme-challenge.doh.brasilpeeringforum.org is an alias for b555d682-7b50-45d9-a92f-3c3d187dd4e7.auth.acme-dns.io.
 +
Para que nosso certificado seja automaticamente renovado colocaremos no '''/etc/crontab''' a seguinte linha abaixo:
 +
00 00  1 * *  root    /usr/bin/certbot -q renew
 +
Acima temos a instrução para renovação automática do certificado. 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 Unbound ===
 +
Em nosso '''/etc/unbound/unbound.conf.d/local.conf''', adicionaremos no bloco "'''server:'''" o seguinte:
 +
interface: 10.10.10.10@443
 +
interface: 10.10.9.9@443
 +
tls-service-key: "/etc/letsencrypt/live/doh.brasilpeeringforum.org/privkey.pem"
 +
tls-service-pem: "/etc/letsencrypt/live/doh.brasilpeeringforum.org/fullchain.pem"
 +
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''': Digite '''chrome://settings/security?search=dns''' no seu Chrome e ative '''Usar DNS seguro''', selecione '''Personalizado''' e adicione nossa URL:
 +
[[Arquivo:Doh bpf2.png|nenhum|commoldura]]
 +
 
 +
== Finalizando ==
 +
Aqui finalizamos nosso projeto para uma Rede de DNS(s) Recursivos Anycast com Hyperlocal. Esse projeto é escalável, seguro, resiliente e você entregará muito mais qualidade de Internet para o seu cliente. Pare de entregar o '''8.8.8.8''' para os seus clientes, você está contribuindo para uma Internet mais lenta, sem a qualidade que o seu cliente merece. Investi meu tempo, que é muito pouco, para deixar esse documento para a comunidade, para você melhorar o seu ISP, para dar um UP! nele, então vamos começar 2023 com o pé direito. O que acha?
 +
 
 +
Como prova de conceito, uma imagem abaixo onde temos uma Rede em produção de DNS(s) Recursivos Anycast e apontando exatamente o momento em que houve alguma situação que fez com que as queries de DNS, convergissem de um node para outro, de forma transparente e automática para o cliente. Podemos notar também que ao ser resolvido o problema, o tráfego retornou para o seu node correto:
 +
[[Arquivo:Convergencia.png|nenhum|commoldura]]
 +
 
 +
== KINDNS (Stands for Knowledge-Sharing and Instantiating Norms for DNS and Naming Security) ==
 +
Achou que havia terminado? Agora que você tem a capacidade de montar uma '''Rede de DNS Recursivo''' com todas essas features acima, com todas as ferramentas que foram comentadas, o que acha de certificar o que fez?
 +
 
 +
Assim como o [https://www.manrs.org/ MANRS] veio para certificar nosso sistema de roteamento na Internet, agora temos o [https://kindns.org/ KINDNS] para certificar que nossos sistemas de DNS estão bem feitos e dentro dos padrões de segurança. Existem '''7 ações''' que podem ser certificadas para nossos DNS Recursivos e estão aqui em https://kindns.org/shared-private-resolvers/. Com essa nossa documentação, se bem aplicada, você pode se candidatar ao KINDNS e ter seu ASN listado aqui https://kindns.org/participants/
 +
 
 +
Obter e manter o '''MANRS''' e '''KINDNS''' demonstra seu compromisso com as Boas Práticas, contribui para termos uma '''Internet''' mais segura e te abre portas para novos negócios que possam exigir essas conformidades.
 +
 
 +
Autor: [[Usuário:Gondim|Marcelo Gondim]]
 +
[[Categoria:Infraestrutura]]
 
__FORCARTDC__
 
__FORCARTDC__

Edição atual tal como às 13h45min de 12 de março de 2024

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:

Dns hierarquia.png

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.brasilpeeringforum.org,

1) O navegador irá requisitar do DNS Recursivo utilizado pelo usuário, o endereço IP que responde pelo nome wiki.brasilpeeringforum.org. Isso porque todos os acessos se dão na Internet através do endereço IP e não através do nome. Imaginem se tivéssemos que decorar os endereços IPs de todos os sites e serviços que quiséssemos acessar na Internet?

2) Nosso DNS Recursivo checa se a informação consta em seu cache. Se a informação existir ela é devolvida ao navegador do usuário e aí este consegue acessar o site.

3) Do contrário o DNS Recursivo pergunta ao Root Server quem é o TLD (Top Level Domain) responsável para atender a requisição.

4) O Root Server informa ao DNS Recursivo o endereço do TLD responsável. No Brasil o TLD responsável pelo .br seria o Registro.br.

5) O DNS Recursivo pergunta ao TLD sobre wiki.brasilpeeringforum.org e este responde com os endereços IP dos DNS Autoritativos responsáveis pelo domínio brasilpeeringforum.org.

6) O DNS Recursivo pergunta aos DNS Autoritativos pelo wiki.brasilpeeringforum.org e este responde com o endereço IP.

7) Por último o DNS Recursivo devolve a informação para o navegador do usuário.

Como que se dá a comunicação entre os DNS(s) Recursivos, Root Servers, TLDs 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 nome para o endereço IP, 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 a 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=Rsvpu5uF2Io

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.

Diagrama dns recursivo.drawio.png

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 4 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:

Grafana dns.png

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. Vamos instalar o unbound do backports porque este já possui suporte ao DoH que veremos mais à frente.

# apt -t bullseye-backports install unbound dns-root-data
# mkdir -p /var/log/unbound
# touch /var/log/unbound/unbound.log
# chown -R unbound:unbound /var/log/unbound/
# systemctl restart unbound

Configurando o logrotate:

cat << EOF > /etc/logrotate.d/unbound
/var/log/unbound/unbound.log {
    rotate 5
    weekly
    postrotate
        unbound-control log_reopen
    endscript
}
EOF

Reiniciando o serviço:

# systemctl restart logrotate.service

Preparando o monitoramento do seu DNS Recursivo

O monitoramento do seu DNS Recursivo é muito importante e para isso vamos usar um template para Zabbix, que modifiquei juntamente com o seu shell script e que enviará os dados para o seu Zabbix server via zabbix-sender. O projeto original está aqui https://github.com/jeftedelima/Unbound-DNS. O xml alterado está aqui https://github.com/gondimcodes/template_zabbix_dns_unbound. Embora seja antigo é perfeitamente importável no Zabbix 6.0, por exemplo.

Teremos um shell script que você precisará colocar no seu /etc/crontab. No exemplo abaixo assumi que o shell script está em /root/scripts. De 5 em 5 minutos os dados serão enviados para o seu Zabbix server.

*/5 * * * *     root    /root/scripts/unboundSend.sh IP_zabbix_server nome_do_host 1> /dev/null

Na linha acima, troque o IP_zabbix_server pelo IP do seu servidor Zabbix e o nome_do_host pelo hostname do seu DNS Recursivo. Você precisará instalar o pacote zabbix-sender no seu DNS Recursivo pois ele será usado para enviar os dados para o Zabbix server.

Abaixo o unboundSend.sh também alterado com inclusão de mais dados:

#!/bin/bash
#       @Jefte de Lima Ferreira
#       jeftedelima at gmail dot com
#       CRON Example
#       Contributor: Marcelo Gondim - gondim at gmail dot com
#       */5   **** root sh /home/dir/unboundSend.sh 192.168.10.1 Unbound 1> /dev/null

if [ -z ${1} ] || [ -z ${2} ] ; then
        echo "You need to specify the IP address of zabbix server and hostname of your DNS Unbound on zabbix"
        echo "Usage example: ./unboundSend.sh 192.168.10.1 UnboundServer"
        exit 1
fi

# ZABBIX_SERVER IP
IP_ZABBIX=$1
# NAME Unbound on Zabbix
NAME_HOST=$2
DIR_TEMP=/var/tmp/
FILE="${DIR_TEMP}dump_unbound_control_stats.txt"
unbound-control stats > ${FILE}

TOTAL_NUM_QUERIES=$(cat ${FILE} | grep -w 'total.num.queries' | cut -d '=' -f2)
TOTAL_NUM_CACHEHITS=$(cat ${FILE} | grep -w 'total.num.cachehits' | cut -d '=' -f2)
TOTAL_NUM_CACHEMISS=$(cat ${FILE} | grep -w 'total.num.cachemiss' | cut -d '=' -f2)
TOTAL_NUM_PREFETCH=$(cat ${FILE} | grep -w 'total.num.prefetch' | cut -d '=' -f2)
TOTAL_NUM_RECURSIVEREPLIES=$(cat ${FILE} | grep -w 'total.num.recursivereplies' | cut -d '=' -f2)

TOTAL_REQ_MAX=$(cat ${FILE} | grep -w 'total.requestlist.max' | cut -d '=' -f2)
TOTAL_REQ_AVG=$(cat ${FILE} | grep -w 'total.requestlist.avg' | cut -d '=' -f2)
TOTAL_REQ_OVERWRITTEN=$(cat ${FILE} | grep -w 'total.requestlist.overwritten' | cut -d '=' -f2)
TOTAL_REQ_EXCEEDED=$(cat ${FILE} | grep -w 'total.requestlist.exceeded' | cut -d '=' -f2)
TOTAL_REQ_CURRENT_ALL=$(cat ${FILE} | grep -w 'total.requestlist.current.all' | cut -d '=' -f2)
TOTAL_REQ_CURRENT_USER=$(cat ${FILE} | grep -w 'total.requestlist.current.user' | cut -d '=' -f2)

TOTAL_TCPUSAGE=$(cat ${FILE} | grep -w 'total.tcpusage' | cut -d '=' -f2)

NUM_QUERY_TYPE_A=$(cat ${FILE} | grep -w 'num.query.type.A' | cut -d '=' -f2)
NUM_QUERY_TYPE_NS=$(cat ${FILE} | grep -w 'num.query.type.NS' | cut -d '=' -f2)
NUM_QUERY_TYPE_MX=$(cat ${FILE} | grep -w 'num.query.type.MX' | cut -d '=' -f2)
NUM_QUERY_TYPE_TXT=$(cat ${FILE} | grep -w 'num.query.type.TXT' | cut -d '=' -f2)
NUM_QUERY_TYPE_PTR=$(cat ${FILE} | grep -w 'num.query.type.PTR' | cut -d '=' -f2)
NUM_QUERY_TYPE_AAAA=$(cat ${FILE} | grep -w 'num.query.type.AAAA' | cut -d '=' -f2)
NUM_QUERY_TYPE_SRV=$(cat ${FILE} | grep -w 'num.query.type.SRV' | cut -d '=' -f2)
NUM_QUERY_TYPE_SOA=$(cat ${FILE} | grep -w 'num.query.type.SOA' | cut -d '=' -f2)
NUM_QUERY_TYPE_HTTPS=$(cat ${FILE} | grep -w 'num.query.type.HTTPS' | cut -d '=' -f2)
NUM_QUERY_TYPE_TYPE0=$(cat ${FILE} | grep -w 'num.query.type.TYPE0' | cut -d '=' -f2)
NUM_QUERY_TYPE_CNAME=$(cat ${FILE} | grep -w 'num.query.type.CNAME' | cut -d '=' -f2)
NUM_QUERY_TYPE_WKS=$(cat ${FILE} | grep -w 'num.query.type.WKS' | cut -d '=' -f2)
NUM_QUERY_TYPE_HINFO=$(cat ${FILE} | grep -w 'num.query.type.HINFO' | cut -d '=' -f2)
NUM_QUERY_TYPE_X25=$(cat ${FILE} | grep -w 'num.query.type.X25' | cut -d '=' -f2)
NUM_QUERY_TYPE_NAPTR=$(cat ${FILE} | grep -w 'num.query.type.NAPTR' | cut -d '=' -f2)
NUM_QUERY_TYPE_DS=$(cat ${FILE} | grep -w 'num.query.type.DS' | cut -d '=' -f2)
NUM_QUERY_TYPE_DNSKEY=$(cat ${FILE} | grep -w 'num.query.type.DNSKEY' | cut -d '=' -f2)
NUM_QUERY_TYPE_TLSA=$(cat ${FILE} | grep -w 'num.query.type.TLSA' | cut -d '=' -f2)
NUM_QUERY_TYPE_SVCB=$(cat ${FILE} | grep -w 'num.query.type.SVCB' | cut -d '=' -f2)
NUM_QUERY_TYPE_SPF=$(cat ${FILE} | grep -w 'num.query.type.SPF' | cut -d '=' -f2)
NUM_QUERY_TYPE_ANY=$(cat ${FILE} | grep -w 'num.query.type.ANY' | cut -d '=' -f2)
NUM_QUERY_TYPE_OTHER=$(cat ${FILE} | grep -w 'num.query.type.other' | cut -d '=' -f2)

NUM_ANSWER_RCODE_NOERROR=$(cat ${FILE} | grep -w 'num.answer.rcode.NOERROR' | cut -d '=' -f2)
NUM_ANSWER_RCODE_NXDOMAIN=$(cat ${FILE} | grep -w 'num.answer.rcode.NXDOMAIN' | cut -d '=' -f2)
NUM_ANSWER_RCODE_SERVFAIL=$(cat ${FILE} | grep -w 'num.answer.rcode.SERVFAIL' | cut -d '=' -f2)
NUM_ANSWER_RCODE_REFUSED=$(cat ${FILE} | grep -w 'num.answer.rcode.REFUSED' | cut -d '=' -f2)
NUM_ANSWER_RCODE_nodata=$(cat ${FILE} | grep -w 'num.answer.rcode.nodata' | cut -d '=' -f2)
NUM_ANSWER_secure=$(cat ${FILE} | grep -w 'num.answer.secure' | cut -d '=' -f2)

#       Sending info to zabbix_server, if variables is not empty!
[ -z ${TOTAL_NUM_QUERIES} ] ||  zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.queries -o ${TOTAL_NUM_QUERIES}
[ -z ${TOTAL_NUM_CACHEHITS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.cachehits -o ${TOTAL_NUM_CACHEHITS}
[ -z ${TOTAL_NUM_CACHEMISS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.cachemiss -o ${TOTAL_NUM_CACHEMISS}
[ -z ${TOTAL_NUM_PREFETCH} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.prefetch -o ${TOTAL_NUM_PREFETCH}
[ -z ${TOTAL_NUM_RECURSIVEREPLIES} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.num.recursivereplies -o ${TOTAL_NUM_RECURSIVEREPLIES}

[ -z ${TOTAL_REQ_MAX} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.max -o ${TOTAL_REQ_MAX}
[ -z ${TOTAL_REQ_AVG} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.avg -o ${TOTAL_REQ_AVG}
[ -z ${TOTAL_REQ_OVERWRITTEN} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.overwritten -o ${TOTAL_REQ_OVERWRITTEN}
[ -z ${TOTAL_REQ_EXCEEDED} ] ||  zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.exceeded -o ${TOTAL_REQ_EXCEEDED}
[ -z ${TOTAL_REQ_CURRENT_ALL} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.current.all -o ${TOTAL_REQ_CURRENT_ALL}
[ -z ${TOTAL_REQ_CURRENT_USER} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.requestlist.current.user -o ${TOTAL_REQ_CURRENT_USER}

[ -z ${TOTAL_TCPUSAGE} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k total.tcpusage -o ${TOTAL_TCPUSAGE}

[ -z ${NUM_QUERY_TYPE_A} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.a -o ${NUM_QUERY_TYPE_A}
[ -z ${NUM_QUERY_TYPE_NS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.ns -o ${NUM_QUERY_TYPE_NS}
[ -z ${NUM_QUERY_TYPE_MX} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.mx -o ${NUM_QUERY_TYPE_MX}
[ -z ${NUM_QUERY_TYPE_TXT} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.txt -o ${NUM_QUERY_TYPE_TXT}
[ -z ${NUM_QUERY_TYPE_PTR} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.ptr -o ${NUM_QUERY_TYPE_PTR}
[ -z ${NUM_QUERY_TYPE_AAAA} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.aaaa -o ${NUM_QUERY_TYPE_AAAA}
[ -z ${NUM_QUERY_TYPE_SRV} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.srv -o ${NUM_QUERY_TYPE_SRV}
[ -z ${NUM_QUERY_TYPE_SOA} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.soa -o ${NUM_QUERY_TYPE_SOA}
[ -z ${NUM_QUERY_TYPE_HTTPS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.https -o ${NUM_QUERY_TYPE_HTTPS}
[ -z ${NUM_QUERY_TYPE_TYPE0} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.type0 -o ${NUM_QUERY_TYPE_TYPE0}
[ -z ${NUM_QUERY_TYPE_CNAME} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.cname -o ${NUM_QUERY_TYPE_CNAME}
[ -z ${NUM_QUERY_TYPE_WKS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.wks -o ${NUM_QUERY_TYPE_WKS}
[ -z ${NUM_QUERY_TYPE_HINFO} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.hinfo -o ${NUM_QUERY_TYPE_HINFO}
[ -z ${NUM_QUERY_TYPE_X25} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.X25 -o ${NUM_QUERY_TYPE_X25}
[ -z ${NUM_QUERY_TYPE_NAPTR} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.naptr -o ${NUM_QUERY_TYPE_NAPTR}
[ -z ${NUM_QUERY_TYPE_DS} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.ds -o ${NUM_QUERY_TYPE_DS}
[ -z ${NUM_QUERY_TYPE_DNSKEY} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.dnskey -o ${NUM_QUERY_TYPE_DNSKEY}
[ -z ${NUM_QUERY_TYPE_TLSA} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.tlsa -o ${NUM_QUERY_TYPE_TLSA}
[ -z ${NUM_QUERY_TYPE_SVCB} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.svcb -o ${NUM_QUERY_TYPE_SVCB}
[ -z ${NUM_QUERY_TYPE_SPF} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.spf -o ${NUM_QUERY_TYPE_SPF}
[ -z ${NUM_QUERY_TYPE_ANY} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.any -o ${NUM_QUERY_TYPE_ANY}
[ -z ${NUM_QUERY_TYPE_OTHER} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.query.other -o ${NUM_QUERY_TYPE_OTHER}

[ -z ${NUM_ANSWER_RCODE_NOERROR} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.NOERROR -o ${NUM_ANSWER_RCODE_NOERROR}
[ -z ${NUM_ANSWER_RCODE_NXDOMAIN} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.NXDOMAIN -o ${NUM_ANSWER_RCODE_NXDOMAIN}
[ -z ${NUM_ANSWER_RCODE_SERVFAIL} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.SERVFAIL -o ${NUM_ANSWER_RCODE_SERVFAIL}
[ -z ${NUM_ANSWER_RCODE_REFUSED} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.REFUSED -o ${NUM_ANSWER_RCODE_REFUSED}
[ -z ${NUM_ANSWER_RCODE_nodata} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.rcode.nodata -o ${NUM_ANSWER_RCODE_nodata}
[ -z ${NUM_ANSWER_secure} ] || zabbix_sender -z ${IP_ZABBIX} -s ${NAME_HOST} -k num.answer.secure -o ${NUM_ANSWER_secure}

No Zabbix será registrado dados como esses abaixo e posteriormente pode ser montado um Grafana com eles:

Zabbix dns01.png
Zabbix dns02.png
Zabbix dns03.png
Zabbix dns04.png

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. Apenas troque <SENHA> por uma senha para fechar o OSPF com mais segurança. Essa senha deve ser usada dos dois lados.

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 message-digest-key 5 md5 <SENHA>
 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
 area 0 authentication message-digest
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 em /etc/unbound/unbound.conf.d/local.conf:

server:
        verbosity: 1
        statistics-interval: 0
        statistics-cumulative: no
        extended-statistics: yes
        num-threads: 4
        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
        so-reuseport: yes
        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: "/usr/share/dns/root.hints"
        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:
 
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

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

Para habilitar o DoH no Unbound é bem simples mas só é possível com a versão 1.17.1 que vem no bullseye-backports e que virá na próxima versão do Debian 12 (Bookworm). O recurso do DoH vem para trazer mais segurança e privacidade para o usuário. É um recurso muito pouco utilizado ainda mas que seu cliente pode vir a pedir algum dia.

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.

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.brasilpeeringforum.org. 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 CNAME no seu DNS Autoritativo para provar que você tem o controle sobre aquele hostname. Antes disso vamos instalar um programa em Python para podermos automatizar nossa renovação de certificado no futuro. Esse programa se encontra aqui mas vou deixá-lo abaixo já modificado o interpretador.

Crie o arquivo /etc/letsencrypt/acme-dns-auth.py com o conteúdo abaixo:

#!/usr/bin/env python3
import json
import os
import requests
import sys

### EDIT THESE: Configuration values ###

# URL to acme-dns instance
ACMEDNS_URL = "https://auth.acme-dns.io"
# Path for acme-dns credential storage
STORAGE_PATH = "/etc/letsencrypt/acmedns.json"
# Whitelist for address ranges to allow the updates from
# Example: ALLOW_FROM = ["192.168.10.0/24", "::1/128"]
ALLOW_FROM = []
# Force re-registration. Overwrites the already existing acme-dns accounts.
FORCE_REGISTER = False

###   DO NOT EDIT BELOW THIS POINT   ###
###         HERE BE DRAGONS          ###

DOMAIN = os.environ["CERTBOT_DOMAIN"]
if DOMAIN.startswith("*."):
    DOMAIN = DOMAIN[2:]
VALIDATION_DOMAIN = "_acme-challenge."+DOMAIN
VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"]


class AcmeDnsClient(object):
    """
    Handles the communication with ACME-DNS API
    """

    def __init__(self, acmedns_url):
        self.acmedns_url = acmedns_url

    def register_account(self, allowfrom):
        """Registers a new ACME-DNS account"""

        if allowfrom:
            # Include whitelisted networks to the registration call
            reg_data = {"allowfrom": allowfrom}
            res = requests.post(self.acmedns_url+"/register",
                                data=json.dumps(reg_data))
        else:
            res = requests.post(self.acmedns_url+"/register")
        if res.status_code == 201:
            # The request was successful
            return res.json()
        else:
            # Encountered an error
            msg = ("Encountered an error while trying to register a new acme-dns "
                   "account. HTTP status {}, Response body: {}")
            print(msg.format(res.status_code, res.text))
            sys.exit(1)

    def update_txt_record(self, account, txt):
        """Updates the TXT challenge record to ACME-DNS subdomain."""
        update = {"subdomain": account['subdomain'], "txt": txt}
        headers = {"X-Api-User": account['username'],
                   "X-Api-Key": account['password'],
                   "Content-Type": "application/json"}
        res = requests.post(self.acmedns_url+"/update",
                            headers=headers,
                            data=json.dumps(update))
        if res.status_code == 200:
            # Successful update
            return
        else:
            msg = ("Encountered an error while trying to update TXT record in "
                   "acme-dns. \n"
                   "------- Request headers:\n{}\n"
                   "------- Request body:\n{}\n"
                   "------- Response HTTP status: {}\n"
                   "------- Response body: {}")
            s_headers = json.dumps(headers, indent=2, sort_keys=True)
            s_update = json.dumps(update, indent=2, sort_keys=True)
            s_body = json.dumps(res.json(), indent=2, sort_keys=True)
            print(msg.format(s_headers, s_update, res.status_code, s_body))
            sys.exit(1)

class Storage(object):
    def __init__(self, storagepath):
        self.storagepath = storagepath
        self._data = self.load()

    def load(self):
        """Reads the storage content from the disk to a dict structure"""
        data = dict()
        filedata = ""
        try:
            with open(self.storagepath, 'r') as fh:
                filedata = fh.read()
        except IOError as e:
            if os.path.isfile(self.storagepath):
                # Only error out if file exists, but cannot be read
                print("ERROR: Storage file exists but cannot be read")
                sys.exit(1)
        try:
            data = json.loads(filedata)
        except ValueError:
            if len(filedata) > 0:
                # Storage file is corrupted
                print("ERROR: Storage JSON is corrupted")
                sys.exit(1)
        return data

    def save(self):
        """Saves the storage content to disk"""
        serialized = json.dumps(self._data)
        try:
            with os.fdopen(os.open(self.storagepath,
                                   os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
                fh.truncate()
                fh.write(serialized)
        except IOError as e:
            print("ERROR: Could not write storage file.")
            sys.exit(1)

    def put(self, key, value):
        """Puts the configuration value to storage and sanitize it"""
        # If wildcard domain, remove the wildcard part as this will use the
        # same validation record name as the base domain
        if key.startswith("*."):
            key = key[2:]
        self._data[key] = value

    def fetch(self, key):
        """Gets configuration value from storage"""
        try:
            return self._data[key]
        except KeyError:
            return None

if __name__ == "__main__":
    # Init
    client = AcmeDnsClient(ACMEDNS_URL)
    storage = Storage(STORAGE_PATH)

    # Check if an account already exists in storage
    account = storage.fetch(DOMAIN)
    if FORCE_REGISTER or not account:
        # Create and save the new account
        account = client.register_account(ALLOW_FROM)
        storage.put(DOMAIN, account)
        storage.save()

        # Display the notification for the user to update the main zone
        msg = "Please add the following CNAME record to your main DNS zone:\n{}"
        cname = "{} CNAME {}.".format(VALIDATION_DOMAIN, account["fulldomain"])
        print(msg.format(cname))

    # Update the TXT record in acme-dns instance
    client.update_txt_record(account, VALIDATION_TOKEN)
# chmod +x /etc/letsencrypt/acme-dns-auth.py

Usaremos a seguinte instrução para criar nosso certificado:

# certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py --preferred-challenges dns --debug-challenges -d doh.brasilpeeringforum.org
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Cert is due for renewal, auto-renewing...
Renewing an existing certificate for doh.brasilpeeringforum.org
Performing the following challenges:
dns-01 challenge for doh.brasilpeeringforum.org
Running manual-auth-hook command: /etc/letsencrypt/acme-dns-auth.py
Output from manual-auth-hook command acme-dns-auth.py:
Please add the following CNAME record to your main DNS zone:
_acme-challenge.doh.brasilpeeringforum.org CNAME b555d682-7b50-45d9-a92f-3c3d187dd4e7.auth.acme-dns.io.

Waiting for verification...

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Challenges loaded. Press continue to submit to CA. Pass "-v" for more info about
challenges.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

Nesse momento você cria o registro CNAME no seu DNS Autoritativo conforme ele solicitou: _acme-challenge.doh.brasilpeeringforum.org IN CNAME b555d682-7b50-45d9-a92f-3c3d187dd4e7.auth.acme-dns.io. e somente depois de criado e checado no DNS, você pressiona o Enter para continuar. Você pode checar dessa forma:

# host -t cname _acme-challenge.doh.brasilpeeringforum.org
_acme-challenge.doh.brasilpeeringforum.org is an alias for b555d682-7b50-45d9-a92f-3c3d187dd4e7.auth.acme-dns.io.

Para que nosso certificado seja automaticamente renovado colocaremos no /etc/crontab a seguinte linha abaixo:

00 00   1 * *   root    /usr/bin/certbot -q renew

Acima temos a instrução para renovação automática do certificado. 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 Unbound

Em nosso /etc/unbound/unbound.conf.d/local.conf, adicionaremos no bloco "server:" o seguinte:

interface: 10.10.10.10@443
interface: 10.10.9.9@443
tls-service-key: "/etc/letsencrypt/live/doh.brasilpeeringforum.org/privkey.pem"
tls-service-pem: "/etc/letsencrypt/live/doh.brasilpeeringforum.org/fullchain.pem"

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: Digite chrome://settings/security?search=dns no seu Chrome e ative Usar DNS seguro, selecione Personalizado e adicione nossa URL:

Doh bpf2.png

Finalizando

Aqui finalizamos nosso projeto para uma Rede de DNS(s) Recursivos Anycast com Hyperlocal. Esse projeto é escalável, seguro, resiliente e você entregará muito mais qualidade de Internet para o seu cliente. Pare de entregar o 8.8.8.8 para os seus clientes, você está contribuindo para uma Internet mais lenta, sem a qualidade que o seu cliente merece. Investi meu tempo, que é muito pouco, para deixar esse documento para a comunidade, para você melhorar o seu ISP, para dar um UP! nele, então vamos começar 2023 com o pé direito. O que acha?

Como prova de conceito, uma imagem abaixo onde temos uma Rede em produção de DNS(s) Recursivos Anycast e apontando exatamente o momento em que houve alguma situação que fez com que as queries de DNS, convergissem de um node para outro, de forma transparente e automática para o cliente. Podemos notar também que ao ser resolvido o problema, o tráfego retornou para o seu node correto:

Convergencia.png

KINDNS (Stands for Knowledge-Sharing and Instantiating Norms for DNS and Naming Security)

Achou que havia terminado? Agora que você tem a capacidade de montar uma Rede de DNS Recursivo com todas essas features acima, com todas as ferramentas que foram comentadas, o que acha de certificar o que fez?

Assim como o MANRS veio para certificar nosso sistema de roteamento na Internet, agora temos o KINDNS para certificar que nossos sistemas de DNS estão bem feitos e dentro dos padrões de segurança. Existem 7 ações que podem ser certificadas para nossos DNS Recursivos e estão aqui em https://kindns.org/shared-private-resolvers/. Com essa nossa documentação, se bem aplicada, você pode se candidatar ao KINDNS e ter seu ASN listado aqui https://kindns.org/participants/

Obter e manter o MANRS e KINDNS demonstra seu compromisso com as Boas Práticas, contribui para termos uma Internet mais segura e te abre portas para novos negócios que possam exigir essas conformidades.

Autor: Marcelo Gondim