Ignorar bloqueios de ILV com DNSTap e BGP


O assunto está bem fora de cena, eu sei. Por exemplo, há um excelente artigo , mas apenas a parte IP da lista de bloqueio é considerada lá. Também adicionaremos domínios.


Devido ao fato de os tribunais e o ILV bloquearem tudo certo e à esquerda, e os fornecedores estão se esforçando para não cair sob as multas emitidas pela “Revizorro” - as perdas associadas aos bloqueios são bastante grandes. Sim, e entre os sites bloqueados "legalmente", existem muitos sites úteis (olá, rutracker)


Eu moro fora da jurisdição da ILV, mas meus pais, parentes e amigos permaneceram em casa. Por isso, decidiu-se criar uma maneira de ignorar bloqueios que é fácil para aqueles que estão longe da TI, de preferência sem a participação deles.


Neste artigo, não descreverei as coisas básicas da rede em etapas, mas descreverei os princípios gerais de como esse esquema pode ser implementado. Portanto, é necessário ter conhecimento de como a rede funciona em geral e no Linux em particular.


Tipos de fechaduras


Primeiro, vamos atualizar o que está bloqueado.


Existem vários tipos de bloqueios no XML paginado do ILV:


  • IP
  • Domínio
  • URL

Por uma questão de simplicidade, reduzimos-os para dois: IP e domínio, e simplesmente retiramos o domínio do bloqueio de URL (mais precisamente, já fizemos isso).


Boas pessoas de Roskomsvoboda implementaram uma API maravilhosa por meio da qual podemos obter o que precisamos:



Acesse sites bloqueados


Para fazer isso, precisamos de alguns pequenos VPS estrangeiros, de preferência com tráfego ilimitado - existem muitos desses 3-5 dólares. Você precisa levá-lo no exterior para que o ping não seja muito grande, mas novamente leve em conta que a Internet e a geografia nem sempre coincidem. E como não há SLA por 5 dólares, é melhor usar mais de 2 peças de diferentes fornecedores para tolerância a falhas.


Em seguida, precisamos configurar o túnel criptografado do roteador do cliente para o VPS. Eu uso o Wireguard como o mais rápido e fácil de configurar desde Eu também tenho roteadores clientes baseados no Linux ( APU2 ou algo no OpenWRT). No caso de alguns Mikrotik / Cisco, você pode usar protocolos disponíveis neles como OpenVPN e GRE-over-IPSEC.


Identificação e redirecionamento de tráfego de interesse


Obviamente, você pode finalizar completamente todo o tráfego da Internet no exterior. Mas, muito provavelmente, a velocidade de trabalhar com conteúdo local sofrerá muito com isso. Além disso, os requisitos de largura de banda no VPS serão muito maiores.


Portanto, precisaremos, de alguma forma, alocar o tráfego para sites bloqueados e enviá-lo seletivamente para o túnel. Mesmo que uma parte do tráfego "extra" chegue lá, ainda é muito melhor do que dirigir tudo através de um túnel.


Para gerenciar o tráfego, usaremos o protocolo BGP e anunciaremos as rotas para as redes necessárias do nosso VPS para os clientes. Como um daemon BGP, vamos considerar o BIRD como um dos mais funcionais e convenientes.


IP


Com o bloqueio de IP, tudo fica claro: acabamos de anunciar todos os IPs bloqueados com VPS. O problema é que existem cerca de 600 mil sub-redes na lista que a API fornece, e a grande maioria delas são / 32 hosts. Esse número de rotas pode ser confuso para roteadores clientes fracos.


Portanto, decidiu-se resumir para a rede / 24 ao processar a lista, se houver 2 ou mais hosts nela. Assim, o número de rotas foi reduzido para ~ 100 mil. O script para isso será o próximo.


Domínios


É mais complicado e existem várias maneiras. Por exemplo, você pode colocar o Squid transparente em cada roteador do cliente e fazer interceptação e espionagem HTTP no handshake TLS para obter a URL solicitada no primeiro caso e o domínio do SNI no segundo.


Mas, devido a todo o novo TLS1.3 + eSNI, a análise HTTPS está se tornando cada vez menos real a cada dia. E a infraestrutura do lado do cliente está se tornando mais complicada - você precisará usar pelo menos o OpenWRT.


Portanto, decidi seguir o caminho de interceptar respostas às consultas de DNS. Aqui, também, qualquer DNS sobre TLS / HTTPS começa a subir acima de nossas cabeças, mas podemos (por enquanto) controlar essa parte no cliente - desative-a ou use nosso próprio servidor para DoT / DoH.


Como interceptar o DNS?


Também pode haver várias abordagens.


  • Interceptação de tráfego DNS através de PCAP ou NFLOG
    Ambos os métodos de interceptação são implementados no utilitário sidmat . Mas ele não é suportado há muito tempo e a funcionalidade é muito primitiva; portanto, você precisa escrever uma ligação nele.
  • Análise de log do servidor DNS
    Infelizmente, os recursores que conheço não sabem como registrar respostas, mas apenas solicitações. Em princípio, isso é lógico, porque, diferentemente das solicitações, as respostas têm uma estrutura complexa e é difícil escrevê-las em forma de texto.
  • DNSTap
    Felizmente, muitos deles já oferecem suporte ao DNSTap para esses fins.

O que é DNSTap?



Este é um protocolo cliente-servidor baseado em buffers de protocolo e fluxos de quadros para transferir de um servidor DNS para um coletor de consultas e respostas DNS estruturadas. De fato, o servidor DNS transmite metadados de solicitações e respostas (tipo de mensagem, IP do cliente / servidor etc.), além de mensagens DNS completas no formato (binário) em que trabalha com eles pela rede.


É importante entender que, no paradigma DNSTap, o servidor DNS atua como um cliente e o coletor como um servidor. Ou seja, o servidor DNS se conecta ao coletor e não vice-versa.


Hoje, o DNSTap é suportado em todos os servidores DNS populares. Mas, por exemplo, o BIND em muitas distribuições (como o Ubuntu LTS) geralmente é criado por algum motivo sem o seu suporte. Portanto, não nos incomodaremos em reconstruir, mas usaremos um recursor mais leve e rápido - Não vinculado.


Como capturar DNSTap?


Existem vários utilitários de CLI para trabalhar com um fluxo de eventos DNSTap, mas eles não funcionam bem para nossa tarefa. Então, decidi inventar minha própria bicicleta que fará o que for necessário: dnstap-bgp


Algoritmo de operação:


  • Quando iniciado, ele carrega uma lista de domínios de um arquivo de texto, os inverte (habr.com -> com.habr), exclui linhas quebradas, duplicatas e subdomínios (ou seja, se habr.com e www.habr.com estiverem na lista - ele será carregado somente o primeiro) e cria uma árvore de prefixos para pesquisa rápida nesta lista
  • Atuando como um servidor DNSTap, aguarda uma conexão do servidor DNS. Em princípio, ele suporta soquetes UNIX e TCP, mas os servidores DNS que conheço só podem usar soquetes UNIX
  • Os pacotes DNSTap recebidos são desserializados primeiro na estrutura do Protobuf e, em seguida, a mensagem DNS binária localizada em um dos campos do Protobuf é analisada para o nível de registros RR do DNS
  • Verifica se o host solicitado (ou seu domínio pai) está na lista carregada; caso contrário, a resposta é ignorada
  • Somente A / AAAA / CNAME RR são selecionados na resposta e os endereços IPv4 / IPv6 correspondentes são extraídos deles.
  • Os endereços IP são armazenados em cache com TTLs personalizados e anunciados para todos os pares BGP configurados
  • Após o recebimento de uma resposta indicando um IP já armazenado em cache - seu TTL é atualizado
  • Após a expiração do TTL, o registro é excluído do cache e dos anúncios do BGP

Funcionalidade adicional:


  • Relendo a lista de domínios por SIGHUP
  • Sincronizando cache com outras instâncias dnstap-bgp via HTTP / JSON
  • Duplicação do cache no disco (no banco de dados BoltDB) para restaurar seu conteúdo após uma reinicialização
  • Suporte para alternar para um namespace de rede diferente (por que isso será descrito abaixo)
  • Suporte IPv6

Limitações:


  • Domínios IDN ainda não suportados
  • Poucas configurações de BGP

Compilei pacotes RPM e DEB para facilitar a instalação. Deve funcionar em todos os sistemas operacionais relativamente recentes com systemd, como eles não têm dependências.


Esquema


Então, vamos começar a montar todos os componentes juntos. Como resultado, devemos obter algo como esta topologia de rede:


A lógica do trabalho, penso, é clara no diagrama:


  • O cliente tem nosso servidor configurado como DNS, e as consultas DNS também devem passar pela VPN. Isso é necessário para que o provedor não possa usar a interceptação de DNS para bloquear.
  • Quando o site é aberto, o cliente envia uma consulta DNS no formato "que IPs possui o xxx.org?"
  • O Unbound resolve o xxx.org (ou pega do cache) e envia uma resposta ao cliente "xxx.org tem tais e tais IPs", duplicando-o em paralelo através do DNSTap
  • O dnstap-bgp anuncia esses endereços no BIRD by BGP se o domínio estiver na lista de bloqueados
  • O BIRD anuncia a rota para esses IPs com o roteador next-hop self cliente
  • Pacotes subsequentes do cliente para esses IPs passam pelo túnel

No servidor, para rotas para sites bloqueados, tenho uma tabela separada dentro do BIRD e ela não se cruza com o sistema operacional.


Há uma desvantagem nesse esquema: o primeiro pacote SYN do cliente provavelmente terá tempo de sair do provedor doméstico desde a rota não é anunciada instantaneamente. E aqui as opções são possíveis, dependendo de como o provedor faz o bloqueio. Se ele simplesmente interrompe o tráfego, não há problemas. E se ele o redirecionar para algum DPI, então (teoricamente) efeitos especiais são possíveis.


Também são possíveis milagres com os clientes que não observam o DNS TTL, o que pode levar o cliente a usar algumas entradas desatualizadas de seu cache podre, em vez de solicitar Unbound.


Na prática, nem o primeiro nem o segundo me causaram problemas, mas sua milhagem pode variar.


Configuração do servidor


Para facilitar a rolagem, escrevi um papel para Ansible . Ele pode configurar servidores e clientes baseados em Linux (projetados para distribuições baseadas em deb). Todas as configurações são bastante óbvias e estão definidas no inventário.yml . Esse papel é retirado do meu grande manual, por isso pode conter erros - solicitações de recebimento bem-vindas :)


Vamos analisar os principais componentes.


BGP


Ao iniciar dois daemons BGP no mesmo host, surge um problema fundamental: o BIRD não deseja aumentar o peering do BGP com um host local (ou com qualquer interface local). Da palavra a todos. Pesquisando e lendo listas de discussão não ajudou, eles alegam que é por design. Talvez exista alguma maneira, mas não a encontrei.


Você pode tentar outro daemon BGP, mas eu gosto do BIRD e ele é usado em todos os lugares comigo, não quero produzir entidades.


Portanto, escondi o dnstap-bgp dentro do namespace da rede, que é conectado à raiz pela interface veth: é como um pipe cujas extremidades se destacam em namespace diferente. Em cada uma dessas extremidades, penduramos endereços IP p2p privados que não saem do host, para que possam ser. Este é o mesmo mecanismo usado para acessar processos dentro do amado Docker e outros contêineres.


Para isso, um script foi escrito e, no dnstap-bgp, foi adicionada a funcionalidade descrita acima para arrastar-se pelos cabelos para outro espaço para nome. Por esse motivo, ele deve ser executado como root ou retornado ao binário CAP_SYS_ADMIN através do comando setcap.


Script de exemplo para criar espaço para nome
 #!/bin/bash NS="dtap" IP="/sbin/ip" IPNS="$IP netns exec $NS $IP" IF_R="veth-$NS-r" IF_NS="veth-$NS-ns" IP_R="192.168.149.1" IP_NS="192.168.149.2" /bin/systemctl stop dnstap-bgp || true $IP netns del $NS > /dev/null 2>&1 $IP netns add $NS $IP link add $IF_R type veth peer name $IF_NS $IP link set $IF_NS netns $NS $IP addr add $IP_R remote $IP_NS dev $IF_R $IP link set $IF_R up $IPNS addr add $IP_NS remote $IP_R dev $IF_NS $IPNS link set $IF_NS up /bin/systemctl start dnstap-bgp 

dnstap-bgp.conf
 namespace = "dtap" domains = "/var/cache/rkn_domains.txt" ttl = "168h" [dnstap] listen = "/tmp/dnstap.sock" perm = "0666" [bgp] as = 65000 routerid = "192.168.149.2" peers = [ "192.168.149.1", ] 

bird.conf
 router id 192.168.1.1; table rkn; # Clients protocol bgp bgp_client1 { table rkn; local as 65000; neighbor 192.168.1.2 as 65000; direct; bfd on; next hop self; graceful restart; graceful restart time 60; export all; import none; } # DNSTap-BGP protocol bgp bgp_dnstap { table rkn; local as 65000; neighbor 192.168.149.2 as 65000; direct; passive on; rr client; import all; export none; } # Static routes list protocol static static_rkn { table rkn; include "rkn_routes.list"; import all; export none; } 

rkn_routes.list
 route 3.226.79.85/32 via "ens3"; route 18.236.189.0/24 via "ens3"; route 3.224.21.0/24 via "ens3"; ... 

DNS


Por padrão, no Ubuntu, o binário Unbound é preso pelo perfil AppArmor, o que impede a conexão com qualquer soquete DNSTap existente. Você pode remover este perfil ou desativá-lo:


 # cd /etc/apparmor.d/disable && ln -s ../usr.sbin.unbound . # apparmor_parser -R /etc/apparmor.d/usr.sbin.unbound 

Provavelmente, isso deve ser adicionado ao manual. É ideal, é claro, corrigir o perfil e emitir os direitos necessários, mas fiquei com preguiça.


unbound.conf
 server: chroot: "" port: 53 interface: 0.0.0.0 root-hints: "/var/lib/unbound/named.root" auto-trust-anchor-file: "/var/lib/unbound/root.key" access-control: 192.168.0.0/16 allow remote-control: control-enable: yes control-use-cert: no dnstap: dnstap-enable: yes dnstap-socket-path: "/tmp/dnstap.sock" dnstap-send-identity: no dnstap-send-version: no dnstap-log-client-response-messages: yes 

Download e processamento de lista


Script para baixar e processar uma lista de endereços IP
Ele baixa a lista, resume antes do prefixo pfx . Em dont_add e dont_summarize, você pode dizer ao IP e às redes para pular ou não resumir. Eu precisava disso porque minha sub-rede VPS estava na lista de bloqueio :)


O engraçado é que a API do RosKomSvoboda bloqueia solicitações com o agente de usuário padrão do Python. Parece que um script infantil conseguiu. Portanto, alteramos para Firelis.


Até agora, ele só funciona com IPv4 porque O IPv6 é pequeno, mas será fácil de corrigir. A menos que seja necessário usar também o bird6.


rkn.py
 #!/usr/bin/python3 import json, urllib.request, ipaddress as ipa url = 'https://api.reserve-rbl.ru/api/v2/ips/json' pfx = '24' dont_summarize = { # ipa.IPv4Network('1.1.1.0/24'), } dont_add = { # ipa.IPv4Address('1.1.1.1'), } req = urllib.request.Request( url, data=None, headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36' } ) f = urllib.request.urlopen(req) ips = json.loads(f.read().decode('utf-8')) prefix32 = ipa.IPv4Address('255.255.255.255') r = {} for i in ips: ip = ipa.ip_network(i) if not isinstance(ip, ipa.IPv4Network): continue addr = ip.network_address if addr in dont_add: continue m = ip.netmask if m != prefix32: r[m] = [addr, 1] continue sn = ipa.IPv4Network(str(addr) + '/' + pfx, strict=False) if sn in dont_summarize: tgt = addr else: tgt = sn if not sn in r: r[tgt] = [addr, 1] else: r[tgt][1] += 1 o = [] for n, v in r.items(): if v[1] == 1: o.append(str(v[0]) + '/32') else: o.append(n) for k in o: print(k) 

Script para atualizar
Começa na minha coroa uma vez por dia, talvez valha a pena puxá-lo a cada 4 horas, porque esse, na minha opinião, é o período de atualização que o ILV exige dos fornecedores. Além disso, existem outros bloqueios urgentes que podem ser executados mais rapidamente.


Faz o seguinte:


  • Executa o primeiro script e atualiza a lista de rotas ( rkn_routes.list ) para o BIRD
  • Recarregar BIRD
  • Atualiza e limpa a lista de domínios para dnstap-bgp
  • Realocar dnstap-bgp

rkn_update.sh
 #!/bin/bash ROUTES="/etc/bird/rkn_routes.list" DOMAINS="/var/cache/rkn_domains.txt" # Get & summarize routes /opt/rkn.py | sed 's/\(.*\)/route \1 via "ens3";/' > $ROUTES.new if [ $? -ne 0 ]; then rm -f $ROUTES.new echo "Unable to download RKN routes" exit 1 fi if [ -e $ROUTES ]; then mv $ROUTES $ROUTES.old fi mv $ROUTES.new $ROUTES /bin/systemctl try-reload-or-restart bird # Get domains curl -s https://api.reserve-rbl.ru/api/v2/domains/json -o - | jq -r '.[]' | sed 's/^\*\.//' | sort | uniq > $DOMAINS.new if [ $? -ne 0 ]; then rm -f $DOMAINS.new echo "Unable to download RKN domains" exit 1 fi if [ -e $DOMAINS ]; then mv $DOMAINS $DOMAINS.old fi mv $DOMAINS.new $DOMAINS /bin/systemctl try-reload-or-restart dnstap-bgp 

Eles foram escritos sem muita reflexão; portanto, se você vir o que pode ser melhorado, tente.


Configuração do cliente


Aqui vou dar exemplos de roteadores Linux, mas no caso do Mikrotik / Cisco isso deve ser ainda mais simples.


Primeiro, configure BIRD:


bird.conf
 router id 192.168.1.2; table rkn; protocol device { scan time 10; }; # Servers protocol bgp bgp_server1 { table rkn; local as 65000; neighbor 192.168.1.1 as 65000; direct; bfd on; next hop self; graceful restart; graceful restart time 60; rr client; export none; import all; } protocol kernel { table rkn; kernel table 222; scan time 10; export all; import none; } 

Assim, sincronizaremos as rotas recebidas do BGP com a tabela de roteamento do kernel número 222.


Depois disso, basta pedir ao kernel que observe esta placa antes de observar o padrão:


 # ip rule add from all pref 256 lookup 222 # ip rule 0: from all lookup local 256: from all lookup 222 32766: from all lookup main 32767: from all lookup default 

Tudo o que resta é configurar o DHCP no roteador para distribuir o endereço IP do túnel do servidor como DNS e o esquema está pronto.


Desvantagens


Com o algoritmo atual para criar e processar uma lista de domínios, o youtube.com e suas CDNs se enquadram nele.


E isso leva ao fato de que todos os vídeos passam pela VPN, que pode entupir todo o canal. Talvez valha a pena compilar uma lista de domínios de exceção populares que estão bloqueados no ILV por enquanto. E pule-os enquanto estiver analisando.


Conclusão


O método descrito permite ignorar quase todos os bloqueios que os provedores implementam atualmente.


Em princípio, o dnstap-bgp pode ser usado para qualquer outra finalidade em que é necessário um certo nível de controle de tráfego baseado em um nome de domínio. Lembre-se de que hoje em dia milhares de sites podem travar no mesmo endereço IP (para alguns Cloudflare, por exemplo), portanto esse método tem uma precisão bastante baixa.


Mas para as necessidades de ignorar bloqueios, isso é suficiente.


Adições, edições, pull-quests são bem-vindos!

Source: https://habr.com/ru/post/pt467547/


All Articles