Evita o bloqueo de ILV con DNSTap e BGP

Evita o bloqueo de ILV con DNSTap e BGP

O tema está bastante revolto, sei. Por exemplo, hai un gran artigo, pero só se considera alí a parte IP da lista de bloqueo. Tamén engadiremos dominios.

Debido a que os xulgados e a RKN bloquean todo a dereita e esquerda, e os provedores están a esforzarse por non caer nas multas de Revizorro, as perdas asociadas ao bloqueo son bastante grandes. E entre os sitios "legalmente" bloqueados hai moitos útiles (ola, rutracker)

Vivo fóra da xurisdición da RKN, pero meus pais, familiares e amigos quedaron na casa. Así que decidiuse crear un xeito sinxelo para que as persoas que están lonxe das TI evitasen o bloqueo, preferiblemente sen a súa participación.

Nesta nota, non describirei por pasos as cousas básicas da rede, senón que describirei os principios xerais de como se pode implementar este esquema. Polo tanto, o coñecemento de como funciona a rede en xeral e en Linux en particular é imprescindible.

Tipos de pechaduras

En primeiro lugar, refresquemos a memoria do que se está bloqueando.

Hai varios tipos de bloqueos no XML descargado do RKN:

  • IP
  • Домен
  • URL

Para simplificar, reducirémolos a dous: IP e dominio, e simplemente eliminaremos o bloqueo do dominio por URL (máis precisamente, xa o fixeron por nós).

boa xente de Roskomsvoboda realizou unha marabillosa API, a través do cal podemos conseguir o que necesitamos:

Acceso a sitios bloqueados

Para iso, necesitamos algúns pequenos VPS estranxeiros, preferiblemente con tráfico ilimitado; hai moitos destes por 3-5 dólares. Cómpre levalo no estranxeiro para que o ping non sexa moi grande, pero, de novo, teña en conta que Internet e xeografía non sempre coinciden. E como non hai un SLA por 5 dólares, é mellor tomar máis de 2 pezas de diferentes provedores para tolerar fallos.

A continuación, necesitamos configurar un túnel cifrado desde o router do cliente ata o VPS. Eu uso Wireguard como o máis rápido e sinxelo de configurar. Tamén teño enrutadores cliente baseados en Linux (APU2 ou algo en OpenWRT). No caso dalgúns Mikrotik / Cisco, pode usar os protocolos dispoñibles neles como OpenVPN e GRE-over-IPSEC.

Identificación e redirección do tráfico de interese

Por suposto, pode desactivar todo o tráfico de Internet a través de países estranxeiros. Pero, o máis probable é que a velocidade de traballo con contido local sufra moito disto. Ademais, os requisitos de ancho de banda en VPS serán moito maiores.

Polo tanto, teremos que asignar dalgunha forma o tráfico aos sitios bloqueados e dirixilo selectivamente ao túnel. Aínda que parte do tráfico "extra" chega ata alí, aínda é moito mellor que conducir todo polo túnel.

Para xestionar o tráfico, utilizaremos o protocolo BGP e anunciaremos rutas ás redes necesarias desde o noso VPS aos clientes. Tomemos BIRD como un dos daemons BGP máis funcionais e cómodos.

IP

Co bloqueo por IP, todo está claro: simplemente anunciamos todas as IP bloqueadas con VPS. O problema é que hai unhas 600 mil subredes na lista que devolve a API, e a gran maioría delas son hosts /32. Este número de rutas pode confundir aos routers clientes débiles.

Polo tanto, ao procesar a lista, decidiuse resumir ata a rede / 24 se ten 2 ou máis hosts. Así, o número de rutas reduciuse a ~ 100 mil. O guión para isto seguirá.

dominios

É máis complicado e hai varias formas. Por exemplo, podes instalar un Squid transparente en cada enrutador cliente e facer a interceptación HTTP alí e mirar o enlace de TLS para obter o URL solicitado no primeiro caso e o dominio de SNI no segundo.

Pero debido a todo tipo de novos TLS1.3 + eSNI, a análise HTTPS é cada día menos real. Si, e a infraestrutura do cliente é cada vez máis complicada: terás que usar polo menos OpenWRT.

Polo tanto, decidín tomar o camiño de interceptar respostas ás solicitudes de DNS. Aquí, tamén, calquera DNS sobre TLS / HTTPS comeza a pasar por riba da túa cabeza, pero podemos (polo momento) controlar esta parte no cliente: desactivala ou usar o teu propio servidor para DoT / DoH.

Como interceptar o DNS?

Tamén aquí pode haber varios enfoques.

  • Interceptación de tráfico DNS vía PCAP o NFLOG
    Ambos métodos de interceptación están implementados na utilidade sidmat. Pero hai moito tempo que non se admitiu e a funcionalidade é moi primitiva, polo que aínda debes escribir un arnés para iso.
  • Análise dos rexistros do servidor DNS
    Desafortunadamente, os recursos que coñezo non poden rexistrar respostas, senón só solicitudes. En principio, isto é lóxico, xa que, a diferenza das solicitudes, as respostas teñen unha estrutura complexa e é difícil escribilas en forma de texto.
  • DNSTap
    Afortunadamente, moitos deles xa admiten DNSTap para este fin.

Que é DNSTap?

Evita o bloqueo de ILV con DNSTap e BGP

É un protocolo cliente-servidor baseado en Protocol Buffers e Frame Streams para transferir desde un servidor DNS a un colector de consultas e respostas DNS estruturadas. Esencialmente, o servidor DNS transmite metadatos de consulta e resposta (tipo de mensaxe, IP de cliente/servidor, etc.) ademais de mensaxes DNS completas na forma (binaria) na que traballa con eles a través da rede.

É importante entender que no paradigma DNSTap, o servidor DNS actúa como cliente e o colector actúa como servidor. É dicir, o servidor DNS conéctase ao colector, e non viceversa.

Hoxe DNSTap é compatible con todos os servidores DNS populares. Pero, por exemplo, BIND en moitas distribucións (como Ubuntu LTS) adoita construírse por algún motivo sen o seu soporte. Polo tanto, non nos molestemos na remontaxe, senón que tome un recurso máis lixeiro e rápido - Unbound.

Como capturar DNSTap?

Ten algunhas número Utilidades CLI para traballar cun fluxo de eventos DNSTap, pero non son axeitados para resolver o noso problema. Por iso, decidín inventar a miña propia bicicleta que fará todo o que sexa necesario: dnstap-bgp

Algoritmo de traballo:

  • Cando se inicia, carga unha lista de dominios desde un ficheiro de texto, invírteos (habr.com -> com.habr), exclúe liñas discontinuas, duplicados e subdominios (é dicir, se a lista contén habr.com e www.habr.com, cargarase só o primeiro) e constrúe unha árbore de prefixos para a busca rápida nesta lista
  • Actuando como servidor DNSTap, espera unha conexión dun servidor DNS. En principio, admite tanto sockets UNIX como TCP, pero os servidores DNS que coñezo só poden usar sockets UNIX
  • Os paquetes DNSTap entrantes son primeiro deserializados nunha estrutura Protobuf, e despois a propia mensaxe DNS binaria, situada nun dos campos Protobuf, analízase ao nivel dos rexistros RR de DNS
  • Compróbase se o host solicitado (ou o seu dominio pai) está na lista cargada, se non, a resposta é ignorada
  • Só se seleccionan da resposta os RR A/AAAA/CNAME e extraen deles os enderezos IPv4/IPv6 correspondentes
  • Os enderezos IP almacénanse na caché con TTL configurable e anúncianse a todos os pares BGP configurados
  • Cando se recibe unha resposta que apunta a unha IP xa almacenada en caché, o seu TTL actualízase
  • Despois de que caduque o TTL, a entrada elimínase da caché e dos anuncios de BGP

Funcionalidade adicional:

  • Relendo a lista de dominios por SIGHUP
  • Manter a caché sincronizada con outras instancias dnstap-bgp vía HTTP/JSON
  • Duplique a caché no disco (na base de datos BoltDB) para restaurar o seu contido despois dun reinicio
  • Soporte para cambiar a un espazo de nomes de rede diferente (a continuación describirase por que é necesario)
  • Soporte IPv6

Limitacións:

  • Os dominios IDN aínda non son compatibles
  • Poucas opcións de BGP

recollín RPM e DEB paquetes para unha fácil instalación. Debería funcionar en todos os sistemas operativos relativamente recentes con systemd. non teñen dependencias.

O esquema

Entón, imos comezar a montar todos os compoñentes xuntos. Como resultado, deberíamos obter algo como esta topoloxía de rede:
Evita o bloqueo de ILV con DNSTap e BGP

A lóxica do traballo, creo, é clara do diagrama:

  • O cliente ten o noso servidor configurado como DNS e as consultas de DNS tamén deben pasar pola VPN. Isto é necesario para que o provedor non poida usar a intercepción DNS para bloquear.
  • Ao abrir o sitio, o cliente envía unha consulta DNS como "cales son as IP de xxx.org"
  • Non obrigado resolve xxx.org (ou sácao da caché) e envía unha resposta ao cliente "xxx.org ten tal IP", duplicándoo en paralelo a través de DNSTap
  • dnstap-bgp anuncia estes enderezos en OBRADOIRO mediante BGP se o dominio está na lista bloqueada
  • OBRADOIRO anuncia unha ruta a estas IP con next-hop self enrutador cliente
  • Os paquetes posteriores do cliente a estas IP pasan polo túnel

No servidor, para as rutas a sitios bloqueados, uso unha táboa separada dentro de BIRD e non se cruza co SO de ningún xeito.

Este esquema ten un inconveniente: o primeiro paquete SYN do cliente, moi probablemente, terá tempo para saír a través do provedor doméstico. a ruta non se anuncia inmediatamente. E aquí as opcións son posibles dependendo de como o provedor faga o bloqueo. Se só deixa o tráfico, entón non hai problema. E se o redirixe a algún DPI, entón (teoricamente) os efectos especiais son posibles.

Tamén é posible que os clientes non respecten os milagres DNS TTL, o que pode facer que o cliente use algunhas entradas obsoletas da súa caché podre en lugar de pedir Unbound.

Na práctica, nin o primeiro nin o segundo me causaron problemas, pero a túa quilometraxe pode variar.

Axuste do servidor

Para facilitar a rodaxe, escribín papel para Ansible. Pode configurar servidores e clientes baseados en Linux (deseñado para distribucións baseadas en deb). Todas as configuracións son bastante obvias e están configuradas inventario.yml. Este papel está cortado do meu libro de xogos grande, polo que pode conter erros - tirar peticións Benvido 🙂

Imos pasar polos principais compoñentes.

BGP

Executar dous daemons BGP no mesmo host ten un problema fundamental: BIRD non quere configurar o peering BGP co localhost (ou calquera interface local). Da palabra en absoluto. Buscar en Google e ler listas de correo non axudou, afirman que isto é por deseño. Quizais haxa algún xeito, pero non o atopei.

Podes probar outro daemon BGP, pero gústame BIRD e úsoo en todas partes, non quero producir entidades.

Polo tanto, agochei dnstap-bgp dentro do espazo de nomes da rede, que está conectado á raíz a través da interface veth: é como un tubo, cuxos extremos sobresaen en diferentes espazos de nomes. En cada un destes extremos, colgamos enderezos IP privados p2p que non van máis alá do host, polo que poden ser calquera cousa. Este é o mesmo mecanismo utilizado para acceder aos procesos dentro amado por todos Docker e outros contedores.

Para iso escribiuse guión e a funcionalidade xa descrita anteriormente para arrastrarse polo pelo a outro espazo de nomes engadiuse a dnstap-bgp. Debido a isto, debe ser executado como root ou emitido ao binario CAP_SYS_ADMIN mediante o comando setcap.

Exemplo de script para crear espazos de nomes

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

paxaro.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 defecto, en Ubuntu, o binario Unbound está fixado polo perfil AppArmor, o que lle prohibe conectarse a todo tipo de sockets DNSTap. Podes eliminar este perfil ou desactivalo:

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

Probablemente debería engadirse ao playbook. O ideal é, por suposto, corrixir o perfil e emitir os dereitos necesarios, pero era demasiado preguiceiro.

desvinculado.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

Descarga e procesamento de listas

Script para descargar e procesar unha lista de enderezos IP
Descarga a lista, suma o prefixo pfx. En non_engadir и non_resumar podes dicir ás IPs e redes que salten ou non resuman. necesitabao. a subrede do meu VPS estaba na lista de bloqueo 🙂

O curioso é que a API de RosKomSvoboda bloquea as solicitudes co axente de usuario predeterminado de Python. Parece que o guionista o conseguiu. Polo tanto, cambiamos a Ognelis.

Ata agora, só funciona con IPv4. a cota de IPv6 é pequena, pero será fácil de arranxar. A menos que teñas que usar bird6 tamén.

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 actualizar
Execúoo na coroa unha vez ao día, quizais paga a pena tiralo cada 4 horas. este, na miña opinión, é o período de renovación que a RKN esixe aos provedores. Ademais, teñen algún outro bloqueo súper urxente, que pode chegar máis rápido.

Fai o seguinte:

  • Executa o primeiro script e actualiza a lista de rutas (rkn_routes.list) para AVE
  • Recarga BIRD
  • Actualiza e limpa a lista de dominios para dnstap-bgp
  • Recargar 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

Foron escritos sen pensar moito, así que se ves algo que se pode mellorar, vai por iso.

Configuración do cliente

Aquí vou dar exemplos de enrutadores Linux, pero no caso de Mikrotik/Cisco debería ser aínda máis sinxelo.

En primeiro lugar, configuramos BIRD:

paxaro.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;
}

Así, sincronizaremos as rutas recibidas de BGP coa táboa de enrutamento do núcleo número 222.

Despois diso, abonda con pedirlle ao núcleo que mire esta placa antes de mirar a predeterminada:

# 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

Todo, queda configurar DHCP no router para distribuír o enderezo IP do túnel do servidor como DNS, e o esquema está listo.

Limitacións

Co algoritmo actual para xerar e procesar a lista de dominios, inclúe, entre outras cousas, youtube.com e os seus CDN.

E isto leva ao feito de que todos os vídeos pasarán pola VPN, o que pode obstruír toda a canle. Quizais paga a pena compilar unha lista de dominios populares: exclusións que bloquean o RKN polo momento, as tripas son finas. E saltaos ao analizar.

Conclusión

O método descrito permítelle evitar case calquera bloqueo que implementen actualmente os provedores.

En principio, dnstap-bgp pódese usar para calquera outro propósito onde se precise algún nivel de control do tráfico en función do nome de dominio. Só ten en conta que na nosa época, mil sitios poden colgar no mesmo enderezo IP (detrás dalgún Cloudflare, por exemplo), polo que este método ten unha precisión bastante baixa.

Pero para as necesidades de evitar bloqueos, isto é suficiente.

Adicións, edicións, solicitudes de extracción: benvido!

Fonte: www.habr.com

Engadir un comentario