Omita el bloqueo de ILV con DNSTap y BGP

Omita el bloqueo de ILV con DNSTap y BGP

El tema está bastante trillado, lo sé. Por ejemplo, hay un gran artículo, pero allí solo se considera la parte IP de la lista de bloqueo. También añadiremos dominios.

Debido al hecho de que los tribunales y la RKN bloquean todo a diestra y siniestra, y los proveedores se esfuerzan por no caer en las multas emitidas por Revizorro, las pérdidas asociadas al bloqueo son bastante grandes. Y entre los sitios bloqueados "legalmente" hay muchos útiles (hola, rutracker)

Vivo fuera de la jurisdicción de la RKN, pero mis padres, familiares y amigos se quedaron en casa. Por lo tanto, se decidió idear una manera fácil para que las personas que no están en TI eviten el bloqueo, preferiblemente sin su participación.

En esta nota, no describiré las cosas básicas de la red en pasos, pero describiré los principios generales de cómo se puede implementar este esquema. Por lo tanto, el conocimiento de cómo funciona la red en general y en Linux en particular es imprescindible.

tipos de cerraduras

Primero, refresquemos nuestra memoria de lo que está siendo bloqueado.

Hay varios tipos de bloqueos en el XML descargado del RKN:

  • IP
  • Nombre de dominio
  • Enlance

Para simplificar, los reduciremos a dos: IP y dominio, y simplemente sacaremos el dominio del bloqueo por URL (más precisamente, ya lo han hecho por nosotros).

buena gente de Roskomsvoboda se dio cuenta de una maravillosa API, a través del cual podemos obtener lo que necesitamos:

Acceso a sitios bloqueados

Para hacer esto, necesitamos algunos VPS extranjeros pequeños, preferiblemente con tráfico ilimitado; hay muchos de estos por 3-5 dólares. Debes llevarlo en el extranjero cercano para que el ping no sea muy grande, pero nuevamente, ten en cuenta que Internet y la geografía no siempre coinciden. Y dado que no hay SLA por 5 dólares, es mejor tomar más de 2 piezas de diferentes proveedores para la tolerancia a fallas.

A continuación, debemos configurar un túnel encriptado desde el enrutador del cliente hasta el VPS. Uso Wireguard como el más rápido y fácil de configurar. También tengo enrutadores de clientes basados ​​en Linux (APU2 o algo en OpenWRT). En el caso de algunos Mikrotik/Cisco, puede usar los protocolos disponibles en ellos como OpenVPN y GRE-over-IPSEC.

Identificación y redirección de tráfico de interés

Por supuesto, puede desactivar todo el tráfico de Internet a través de países extranjeros. Pero, muy probablemente, la velocidad de trabajar con contenido local sufrirá mucho por esto. Además, los requisitos de ancho de banda en VPS serán mucho más altos.

Por lo tanto, tendremos que asignar de alguna manera el tráfico a los sitios bloqueados y dirigirlo selectivamente al túnel. Incluso si parte del tráfico "extra" llega allí, sigue siendo mucho mejor que conducir todo a través del túnel.

Para gestionar el tráfico, utilizaremos el protocolo BGP y anunciaremos las rutas a las redes necesarias desde nuestro VPS a los clientes. Tomemos a BIRD como uno de los demonios BGP más funcionales y convenientes.

IP

Con el bloqueo por IP, todo está claro: simplemente anunciamos todas las IP bloqueadas con VPS. El problema es que hay unas 600 mil subredes en la lista que devuelve la API, y la gran mayoría son hosts /32. Esta cantidad de rutas puede confundir a los enrutadores de clientes débiles.

Por lo tanto, al procesar la lista, se decidió resumir hasta la red /24 si tiene 2 o más hosts. Así, el número de rutas se redujo a ~100 mil. El guión para esto seguirá.

dominios

Es más complicado y hay varias formas. Por ejemplo, puede instalar un Squid transparente en cada enrutador de cliente y hacer una intercepción HTTP allí y mirar el protocolo de enlace TLS para obtener la URL solicitada en el primer caso y el dominio de SNI en el segundo.

Pero debido a todo tipo de TLS1.3 + eSNI novedosos, el análisis de HTTPS se está volviendo cada vez menos real cada día. Sí, y la infraestructura del lado del cliente se está volviendo más complicada: deberá usar al menos OpenWRT.

Por lo tanto, decidí tomar el camino de interceptar respuestas a consultas de DNS. Aquí, también, cualquier DNS sobre TLS/HTTPS comienza a flotar sobre su cabeza, pero podemos (por ahora) controlar esta parte en el cliente, ya sea desactivándola o usando su propio servidor para DoT/DoH.

¿Cómo interceptar DNS?

Aquí, también, puede haber varios enfoques.

  • Intercepción de tráfico DNS a través de PCAP o NFLOG
    Ambos métodos de interceptación se implementan en la utilidad Sidmat. Pero no ha sido compatible durante mucho tiempo y la funcionalidad es muy primitiva, por lo que aún necesita escribir un arnés para ello.
  • Análisis de registros del servidor DNS
    Desafortunadamente, los recursors que conozco no pueden registrar respuestas, sino solo solicitudes. En principio, esto es lógico, ya que, a diferencia de las solicitudes, las respuestas tienen una estructura compleja y es difícil escribirlas en forma de texto.
  • DNSTap
    Afortunadamente, muchos de ellos ya admiten DNSTap para este propósito.

¿Qué es DNSTap?

Omita el bloqueo de ILV con DNSTap y BGP

Es un protocolo cliente-servidor basado en Protocol Buffers y Frame Streams para transferir desde un servidor DNS a un colector de consultas y respuestas DNS estructuradas. Esencialmente, el servidor DNS transmite metadatos de consultas y respuestas (tipo de mensaje, IP de cliente/servidor, etc.) además de mensajes DNS completos en el formato (binario) en el que trabaja con ellos a través de la red.

Es importante comprender que en el paradigma DNSTap, el servidor DNS actúa como cliente y el recopilador actúa como servidor. Es decir, el servidor DNS se conecta al recopilador y no al revés.

Hoy, DNSTap es compatible con todos los servidores DNS populares. Pero, por ejemplo, BIND en muchas distribuciones (como Ubuntu LTS) a menudo se construye por alguna razón sin su soporte. Así que no nos molestemos en volver a ensamblar, pero tomemos un recursor más ligero y rápido: Unbound.

¿Cómo capturar DNSTap?

Hay algunos número Las utilidades CLI para trabajar con una secuencia de eventos DNSTap, pero no son adecuadas para resolver nuestro problema. Por lo tanto, decidí inventar mi propia bicicleta que hará todo lo necesario: dnstap-bgp

Algoritmo de trabajo:

  • Cuando se inicia, carga una lista de dominios desde un archivo de texto, los invierte (habr.com -> com.habr), excluye líneas discontinuas, duplicados y subdominios (es decir, si la lista contiene habr.com y www.habr.com, se cargará solo el primero) y construye un árbol de prefijos para una búsqueda rápida a través de esta lista
  • Actuando como un servidor DNSTap, espera una conexión desde un servidor DNS. En principio, admite sockets UNIX y TCP, pero los servidores DNS que conozco solo pueden usar sockets UNIX
  • Los paquetes DNSTap entrantes primero se deserializan en una estructura Protobuf, y luego el mensaje DNS binario en sí, ubicado en uno de los campos Protobuf, se analiza al nivel de los registros DNS RR.
  • Se comprueba si el host solicitado (o su dominio principal) está en la lista cargada, de lo contrario, se ignora la respuesta
  • Solo se seleccionan los RR A/AAAA/CNAME de la respuesta y se extraen de ellos las direcciones IPv4/IPv6 correspondientes
  • Las direcciones IP se almacenan en caché con TTL configurable y se anuncian a todos los pares BGP configurados
  • Al recibir una respuesta que apunta a una IP ya almacenada en caché, su TTL se actualiza
  • Una vez que caduca el TTL, la entrada se elimina de la memoria caché y de los anuncios de BGP.

Funcionalidad adicional:

  • Releyendo la lista de dominios por SIGHUP
  • Mantener el caché sincronizado con otras instancias dnstap-bgp a través de HTTP/JSON
  • Duplique el caché en el disco (en la base de datos de BoltDB) para restaurar su contenido después de reiniciar
  • Soporte para cambiar a un espacio de nombres de red diferente (a continuación se describe por qué es necesario)
  • Compatibilidad con IPv6

Limitaciones:

  • Los dominios IDN aún no son compatibles
  • Pocas configuraciones de BGP

yo coleccióno RPM y DEB paquetes para una fácil instalación. Debería funcionar en todos los sistemas operativos relativamente recientes con systemd. no tienen ninguna dependencia.

esquema

Entonces, comencemos a ensamblar todos los componentes juntos. Como resultado, deberíamos obtener algo como esta topología de red:
Omita el bloqueo de ILV con DNSTap y BGP

La lógica del trabajo, creo, queda clara en el diagrama:

  • El cliente tiene nuestro servidor configurado como DNS, y las consultas de DNS también deben pasar por la VPN. Esto es necesario para que el proveedor no pueda usar la interceptación de DNS para bloquear.
  • Al abrir el sitio, el cliente envía una consulta de DNS como "¿cuáles son las IP de xxx.org?"
  • Sin consolidar resuelve xxx.org (o lo toma de la caché) y envía una respuesta al cliente "xxx.org tiene tal o cual IP", duplicándolo en paralelo a través de DNSTap
  • dnstap-bgp anuncia estas direcciones en PÁJARO a través de BGP si el dominio está en la lista de bloqueados
  • PÁJARO anuncia una ruta a estas direcciones IP con next-hop self enrutador cliente
  • Los paquetes posteriores del cliente a estas direcciones IP pasan por el túnel

En el servidor, para las rutas a los sitios bloqueados, uso una tabla separada dentro de BIRD y no se cruza con el sistema operativo de ninguna manera.

Este esquema tiene un inconveniente: lo más probable es que el primer paquete SYN del cliente tenga tiempo de salir a través del proveedor nacional. la ruta no se anuncia inmediatamente. Y aquí las opciones son posibles dependiendo de cómo el proveedor realiza el bloqueo. Si solo deja caer el tráfico, entonces no hay problema. Y si lo redirige a algún DPI, entonces (teóricamente) los efectos especiales son posibles.

También es posible que los clientes no respeten los milagros de TTL de DNS, lo que puede hacer que el cliente use algunas entradas obsoletas de su caché podrida en lugar de solicitar Unbound.

En la práctica, ni el primero ni el segundo me causaron problemas, pero su kilometraje puede variar.

Ajuste del servidor

Para facilitar el enrollado, escribí papel para Ansible. Puede configurar tanto servidores como clientes basados ​​en Linux (diseñado para distribuciones basadas en deb). Todos los ajustes son bastante obvios y se establecen en inventario.yml. Este rol se eliminó de mi gran libro de jugadas, por lo que puede contener errores: solicitudes de extracción bienvenido 🙂

Repasemos los componentes principales.

BGP

Ejecutar dos demonios BGP en el mismo host tiene un problema fundamental: BIRD no quiere configurar el emparejamiento BGP con el host local (o cualquier interfaz local). De la palabra en absoluto. Buscar en Google y leer listas de correo no ayudó, afirman que esto es así por diseño. Quizás haya alguna forma, pero no la encontré.

Puede probar con otro demonio BGP, pero me gusta BIRD y lo uso en todas partes, no quiero producir entidades.

Por lo tanto, escondí dnstap-bgp dentro del espacio de nombres de la red, que está conectado a la raíz a través de la interfaz veth: es como una tubería, cuyos extremos sobresalen en diferentes espacios de nombres. En cada uno de estos extremos, colgamos direcciones IP p2p privadas que no van más allá del host, por lo que pueden ser cualquier cosa. Este es el mismo mecanismo utilizado para acceder a los procesos dentro amado por todos Docker y otros contenedores.

Para esto fue escrito guión y se agregó a dnstap-bgp la funcionalidad ya descrita anteriormente para arrastrarse del cabello a otro espacio de nombres. Debido a esto, debe ejecutarse como raíz o enviarse al binario CAP_SYS_ADMIN a través del comando setcap.

Script de ejemplo para crear un espacio de nombres

#!/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ájaro.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.lista

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

De forma predeterminada, en Ubuntu, el binario Unbound está sujeto al perfil AppArmor, que le prohíbe conectarse a todo tipo de sockets DNSTap. Puede eliminar este perfil o desactivarlo:

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

Esto probablemente debería agregarse al libro de jugadas. Es ideal, por supuesto, corregir el perfil y emitir los derechos necesarios, pero me dio mucha flojera.

desatado.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 y procesamiento de listas

Script para descargar y procesar una lista de direcciones IP
Descarga la lista, suma el prefijo pfj. En no_añadir и no_resumir puede decirle a las IP y redes que se salten o que no se resuman. Lo necesitaba. la subred de mi VPS estaba en la lista de bloqueo 🙂

Lo curioso es que la API de RosKomSvoboda bloquea las solicitudes con el agente de usuario predeterminado de Python. Parece que el script-kiddy lo entendió. Por lo tanto, lo cambiamos a Ognelis.

Hasta ahora, solo funciona con IPv4. la proporción de IPv6 es pequeña, pero será fácil de arreglar. A menos que tengas que usar bird6 tambié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)

Guión para actualizar
Lo ejecuto en la corona una vez al día, tal vez valga la pena tirar de él cada 4 horas. este, en mi opinión, es el período de renovación que la RKN exige a los proveedores. Además, tienen algún otro bloqueo súper urgente, que puede llegar más rápido.

Hace lo siguiente:

  • Ejecuta el primer script y actualiza la lista de rutas (rkn_routes.list) para PÁJARO
  • Recargar PÁJARO
  • Actualiza y limpia la 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

Fueron escritos sin pensarlo mucho, así que si ves algo que se puede mejorar, hazlo.

Configuración del cliente

Aquí daré ejemplos para enrutadores Linux, pero en el caso de Mikrotik/Cisco debería ser aún más fácil.

Primero, configuramos BIRD:

pájaro.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 las rutas recibidas de BGP con la tabla de enrutamiento del kernel número 222.

Después de eso, basta con pedirle al kernel que mire esta placa antes de mirar la 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 en el enrutador para distribuir la dirección IP del túnel del servidor como DNS, y el esquema está listo.

Limitaciones

Con el algoritmo actual para generar y procesar la lista de dominios, incluye, entre otras cosas, youtube.com y sus CDN.

Y esto lleva al hecho de que todos los videos pasarán por la VPN, lo que puede obstruir todo el canal. Tal vez valga la pena compilar una lista de exclusiones de dominios populares que bloquean el RKN por el momento, las agallas son delgadas. Y sáltelos al analizar.

Conclusión

El método descrito le permite eludir casi cualquier bloqueo que los proveedores implementen actualmente.

En principio, los dnstap-bgp se puede utilizar para cualquier otro propósito en el que se necesite cierto nivel de control de tráfico en función del nombre de dominio. Solo tenga en cuenta que en nuestro tiempo, miles de sitios pueden colgarse de la misma dirección IP (detrás de algunos Cloudflare, por ejemplo), por lo que este método tiene una precisión bastante baja.

Pero para las necesidades de eludir bloqueos, esto es suficiente.

Adiciones, ediciones, solicitudes de incorporación de cambios: ¡bienvenidos!

Fuente: habr.com

Añadir un comentario