Contorne o bloqueio de ILV com DNSTap e BGP

Contorne o bloqueio de ILV com DNSTap e BGP

O assunto está bem batido, eu sei. Por exemplo, existe um grande artigo, mas apenas a parte do IP da lista de bloqueio é considerada lá. Também adicionaremos domínios.

Devido ao fato de que os tribunais e o RKN bloqueiam tudo a torto e a direito, e os provedores estão se esforçando para não cair nas multas emitidas pelo Revizorro, as perdas associadas ao bloqueio são bastante grandes. E entre os sites bloqueados "legalmente", existem muitos úteis (olá, rutracker)

Moro fora da jurisdição do RKN, mas meus pais, parentes e amigos permaneceram em casa. Portanto, decidiu-se criar uma maneira fácil para pessoas distantes da TI contornarem o bloqueio, de preferência sem a participação delas.

Nesta nota, 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, o conhecimento de como a rede funciona em geral e no Linux em particular é obrigatório.

tipos de fechaduras

Primeiro, vamos refrescar nossa memória do que está sendo bloqueado.

Existem vários tipos de bloqueios no XML descarregado do RKN:

  • IP
  • Nome de domínio
  • URL

Para simplificar, vamos reduzi-los a dois: IP e domínio, e simplesmente retirar o domínio do bloqueio por URL (mais precisamente, eles já fizeram isso por nós).

gente boa de Roskomsvoboda percebeu um maravilhoso API, através do qual podemos obter o que precisamos:

Acesso a sites bloqueados

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

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

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

Você pode, é claro, desativar todo o tráfego da Internet em países estrangeiros. Mas, muito provavelmente, a velocidade de trabalho 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 alocar de alguma forma o tráfego para sites bloqueados e direcioná-lo seletivamente para o túnel. Mesmo que algum tráfego "extra" chegue lá, ainda é muito melhor do que dirigir tudo pelo túnel.

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

IP

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

Portanto, ao processar a lista, optou-se por resumir até a rede /24 se tiver 2 ou mais hosts. Assim, o número de rotas foi reduzido para ~100 mil. O script para isso seguirá.

domínios

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

Mas, devido a todos os tipos de TLS1.3 + eSNI, a análise de HTTPS está se tornando cada vez menos real a cada dia. Sim, e a infraestrutura do lado do cliente está ficando mais complicada - você terá que usar pelo menos o OpenWRT.

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

Como interceptar DNS?

Aqui, também, pode haver várias abordagens.

  • Interceptação de tráfego DNS via PCAP ou NFLOG
    Ambos os métodos de interceptação são implementados no utilitário sidmat. Mas não tem suporte há muito tempo e a funcionalidade é muito primitiva, então você ainda precisa escrever um arnês para ele.
  • Análise de logs do servidor DNS
    Infelizmente, os recursors que conheço não conseguem registrar respostas, mas apenas solicitações. Em princípio, isso é lógico, pois, ao contrário dos pedidos, 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 essa finalidade.

O que é DNSTap?

Contorne o bloqueio de ILV com DNSTap e BGP

É um protocolo cliente-servidor baseado em Protocol Buffers e Frame Streams para transferência de um servidor DNS para um coletor de consultas e respostas DNS estruturadas. Essencialmente, o servidor DNS transmite metadados de consulta e resposta (tipo de mensagem, IP cliente/servidor, etc.) além de mensagens DNS completas na forma (binária) em que trabalha com eles na rede.

É importante entender que no paradigma DNSTap, o servidor DNS atua como cliente e o coletor atua como 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 é construído por algum motivo sem seu suporte. Portanto, não vamos nos preocupar com a remontagem, mas sim com um recursor mais leve e rápido - Unbound.

Como pegar o DNSTap?

Tem alguns número Utilitários CLI para trabalhar com um fluxo de eventos DNSTap, mas não são adequados para resolver nosso problema. Portanto, decidi inventar minha própria bicicleta que fará tudo o que for necessário: dnstap-bgp

Algoritmo de trabalho:

  • Quando iniciado, ele carrega uma lista de domínios de um arquivo de texto, inverte-os (habr.com -> com.habr), exclui linhas quebradas, duplicatas e subdomínios (ou seja, se a lista contiver habr.com e www.habr.com, será carregado apenas o primeiro) e constrói uma árvore de prefixos para uma busca rápida nesta lista
  • Atuando como um servidor DNSTap, ele aguarda uma conexão de um 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 primeiro desserializados em uma estrutura Protobuf e, em seguida, a própria mensagem DNS binária, localizada em um dos campos Protobuf, é analisada no nível dos registros DNS RR
  • É verificado se o host solicitado (ou seu domínio pai) está na lista carregada, caso contrário, a resposta é ignorada
  • Somente RRs A/AAAA/CNAME são selecionados da resposta e os endereços IPv4/IPv6 correspondentes são extraídos deles
  • Os endereços IP são armazenados em cache com TTL configurável e anunciados para todos os pares BGP configurados
  • Ao receber uma resposta apontando para um IP já armazenado em cache, seu TTL é atualizado
  • Depois que o TTL expira, a entrada é removida do cache e dos anúncios do BGP

Funcionalidade adicional:

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

Restrições:

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

eu coletei RPM e DEB pacotes para fácil instalação. Deve funcionar em todos os sistemas operacionais relativamente recentes com systemd. eles não têm nenhuma dependência.

Condução

Então, vamos começar a montar todos os componentes juntos. Como resultado, devemos obter algo como esta topologia de rede:
Contorne o bloqueio de ILV com DNSTap e BGP

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

  • O cliente tem nosso servidor configurado como DNS, e as consultas de 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.
  • Ao abrir o site, o cliente envia uma consulta de DNS como “quais são os IPs de xxx.org”
  • Não consolidado resolve xxx.org (ou pega do cache) e envia uma resposta para o cliente “xxx.org tem tal e tal IP”, duplicando-o em paralelo via DNSTap
  • dnstap-bgp anuncia estes endereços em PÁSSARO via BGP se o domínio estiver na lista de bloqueados
  • PÁSSARO anuncia uma rota para esses IPs com next-hop self roteador cliente
  • Os pacotes subseqüentes do cliente para esses IPs passam pelo túnel

No servidor, para rotas para sites bloqueados, uso uma tabela separada dentro do BIRD e ela não se cruza com o SO de forma alguma.

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

Também é possível que os clientes não respeitem os milagres DNS TTL, o que pode fazer com que o cliente use algumas entradas obsoletas de seu cache podre em vez de solicitar Unbound.

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

Ajuste do servidor

Para facilitar a rolagem, escrevi papel para Ansible. Ele pode configurar servidores e clientes baseados em Linux (projetado para distribuições baseadas em deb). Todas as configurações são bastante óbvias e são definidas em inventário.yml. Esta função foi cortada do meu manual grande, por isso pode conter erros - puxar pedidos bem vindo 🙂

Vamos ver os componentes principais.

BGP

A execução de dois daemons BGP no mesmo host tem um problema fundamental: o BIRD não deseja configurar o emparelhamento BGP com o host local (ou qualquer interface local). Da palavra em tudo. Pesquisar no Google e ler listas de discussão não ajudou, eles afirmam que isso é intencional. Talvez haja alguma maneira, mas não a encontrei.

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

Portanto, escondi dnstap-bgp dentro do namespace da rede, que é conectado à raiz por meio da interface veth: é como um cano, cujas pontas se projetam em diferentes namespaces. Em cada uma dessas extremidades, penduramos endereços IP p2p privados que não vão além do host, para que possam ser qualquer coisa. Este é o mesmo mecanismo usado para acessar processos dentro amado por todos Docker e outros contêineres.

Para isso foi escrito escrita e a funcionalidade já descrita acima para se arrastar pelos cabelos para outro namespace foi adicionada ao dnstap-bgp. Por causa disso, ele deve ser executado como root ou enviado para o binário CAP_SYS_ADMIN por meio do comando setcap.

Exemplo de script para criar namespace

#!/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",
]

pássaro.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 é bloqueado pelo perfil AppArmor, que o proíbe de se conectar a todos os tipos de soquetes DNSTap. Você pode excluir este perfil ou desativá-lo:

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

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

não consolidado.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

Baixando e processando listas

Script para baixar e processar uma lista de endereços IP
Ele baixa a lista, soma o prefixo pfx. Em não_adicionar и não_resumir você pode dizer aos IPs e redes para pular ou não resumir. Eu precisava disso. a sub-rede do meu VPS estava na lista de bloqueio 🙂

O engraçado é que a API RosKomSvoboda bloqueia solicitações com o agente de usuário Python padrão. Parece que o script-kiddy entendeu. Portanto, mudamos para Ognelis.

Até agora, só funciona com IPv4. a participação do IPv6 é pequena, mas será fácil de corrigir. A menos que você tenha que usar bird6 também.

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
Eu corro na coroa uma vez por dia, talvez valha a pena puxá-lo a cada 4 horas. este, na minha opinião, é o período de renovação que o RKN exige dos provedores. Além disso, eles possuem algum outro bloqueio superurgente, que pode chegar mais rápido.

Faz o seguinte:

  • Executa o primeiro script e atualiza a lista de rotas (rkn_routes.list) para PÁSSARO
  • Recarregar PÁSSARO
  • Atualiza e limpa a lista de domínios para dnstap-bgp
  • Recarregar 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 pensar muito, então se você vir algo que pode ser melhorado - vá em frente.

Configuração do cliente

Aqui darei exemplos para roteadores Linux, mas no caso do Mikrotik/Cisco deve ser ainda mais fácil.

Primeiro, configuramos o BIRD:

pássaro.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 olhe para esta placa antes de olhar para a 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, resta configurar o DHCP no roteador para distribuir o endereço IP do túnel do servidor como DNS, e o esquema está pronto.

Contras:

Com o algoritmo atual para gerar e processar a lista de domínios, inclui, entre outras coisas, youtube.com e seus CDNs.

E isso leva ao fato de que todos os vídeos passarão pela VPN, o que pode entupir todo o canal. Talvez valha a pena compilar uma lista de exclusões de domínios populares que bloqueiam o RKN por enquanto, as entranhas são finas. E pule-os ao analisar.

Conclusão

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

Em princípio, dnstap-bgp pode ser usado para qualquer outra finalidade em que algum nível de controle de tráfego seja necessário com base no nome de domínio. Lembre-se de que, em nosso tempo, mil sites podem travar no mesmo endereço IP (atrás de algum Cloudflare, por exemplo), portanto, esse método tem uma precisão bastante baixa.

Mas para as necessidades de contornar bloqueios, isso é o suficiente.

Adições, edições, pull requests - bem-vindo!

Fonte: habr.com

Adicionar um comentário