Umgehen Sie die ILV-Blockierung mit DNSTap und BGP

Umgehen Sie die ILV-Blockierung mit DNSTap und BGP

Das Thema ist ziemlich abgedroschen, ich weiß. Es gibt zum Beispiel eine tolle Beitrag, allerdings wird dort nur der IP-Teil der Blocklist berücksichtigt. Wir werden auch Domains hinzufügen.

Aufgrund der Tatsache, dass die Gerichte und das RKN alles rechts und links blockieren und die Anbieter sich bemühen, nicht unter die von Revizorro verhängten Bußgelder zu fallen, sind die damit verbundenen Verluste durch die Sperrung recht groß. Und unter den „rechtmäßig“ gesperrten Seiten gibt es viele nützliche (Hallo, Rutracker)

Ich lebe außerhalb des Zuständigkeitsbereichs des RKN, aber meine Eltern, Verwandten und Freunde sind zu Hause geblieben. Deshalb wurde beschlossen, eine einfache Möglichkeit zu finden, wie IT-ferne Personen die Blockierung umgehen können, vorzugsweise ohne deren Beteiligung.

In dieser Anmerkung werde ich die grundlegenden Netzwerkdinge nicht schrittweise beschreiben, sondern die allgemeinen Prinzipien, wie dieses Schema implementiert werden kann. Kenntnisse darüber, wie das Netzwerk im Allgemeinen und unter Linux im Besonderen funktioniert, sind daher ein Muss.

Arten von Schlössern

Lassen Sie uns zunächst unsere Erinnerung daran auffrischen, was blockiert wird.

Es gibt verschiedene Arten von Sperren im entladenen XML aus dem RKN:

  • IP
  • Домен
  • URL

Der Einfachheit halber reduzieren wir sie auf zwei: IP und Domain, und wir ziehen einfach die Domain aus der URL-Blockierung heraus (genauer gesagt, sie haben dies bereits für uns erledigt).

gute Leute aus Roskomsvoboda realisierte ein wunderbares API, durch die wir bekommen können, was wir brauchen:

Zugriff auf gesperrte Seiten

Dazu benötigen wir einige kleine ausländische VPS, vorzugsweise mit unbegrenztem Traffic – davon gibt es viele für 3-5 Dollar. Sie müssen es im nahen Ausland nehmen, damit der Ping nicht sehr groß ist. Beachten Sie jedoch auch hier, dass Internet und Geografie nicht immer übereinstimmen. Und da es kein SLA für 5 Dollar gibt, ist es aus Gründen der Fehlertoleranz besser, zwei oder mehr Teile von verschiedenen Anbietern zu nehmen.

Als nächstes müssen wir einen verschlüsselten Tunnel vom Client-Router zum VPS einrichten. Ich verwende Wireguard, weil es am schnellsten und am einfachsten einzurichten ist. Ich habe auch Client-Router, die auf Linux basieren (APU2 oder so etwas in OpenWRT). Bei einigen Mikrotik/Cisco können Sie die dort verfügbaren Protokolle wie OpenVPN und GRE-over-IPSEC verwenden.

Identifizierung und Umleitung des Datenverkehrs von Interesse

Selbstverständlich können Sie den gesamten Internetverkehr im Ausland abschalten. Aber höchstwahrscheinlich wird die Geschwindigkeit der Arbeit mit lokalen Inhalten dadurch stark beeinträchtigt. Außerdem werden die Bandbreitenanforderungen für VPS viel höher sein.

Daher müssen wir den Datenverkehr irgendwie blockierten Standorten zuweisen und ihn selektiv zum Tunnel leiten. Selbst wenn ein Teil des „zusätzlichen“ Verkehrs dort ankommt, ist es immer noch viel besser, als alles durch den Tunnel zu fahren.

Um den Datenverkehr zu verwalten, verwenden wir das BGP-Protokoll und geben Routen zu den erforderlichen Netzwerken von unserem VPS an die Clients bekannt. Nehmen wir BIRD als einen der funktionalsten und bequemsten BGP-Daemons.

IP

Bei der Blockierung nach IP ist alles klar: Wir geben einfach alle blockierten IPs mit VPS bekannt. Das Problem besteht darin, dass die von der API zurückgegebene Liste etwa 600 Subnetze enthält und die überwiegende Mehrheit davon /32-Hosts sind. Diese Anzahl an Routen kann schwache Client-Router verwirren.

Daher wurde bei der Verarbeitung der Liste beschlossen, bis zum Netzwerk / 24 zusammenzufassen, wenn es über 2 oder mehr Hosts verfügt. Dadurch wurde die Anzahl der Routen auf ~100 reduziert. Das Skript dazu folgt.

Domains

Es ist komplizierter und es gibt mehrere Möglichkeiten. Sie können beispielsweise einen transparenten Squid auf jedem Client-Router installieren und dort HTTP-Abfangen und einen Blick in den TLS-Handshake durchführen, um im ersten Fall die angeforderte URL und im zweiten Fall die Domäne von SNI zu erhalten.

Aber aufgrund aller möglichen neuen TLS1.3 + eSNI wird die HTTPS-Analyse von Tag zu Tag weniger real. Ja, und die Infrastruktur auf der Clientseite wird immer komplizierter – Sie müssen mindestens OpenWRT verwenden.

Deshalb habe ich mich für den Weg des Abfangens von Antworten auf DNS-Anfragen entschieden. Auch hier beginnt jegliches DNS-over-TLS/HTTPS über Ihrem Kopf zu schweben, aber wir können diesen Teil (vorerst) auf dem Client steuern – entweder deaktivieren oder Ihren eigenen Server für DoT/DoH verwenden.

Wie kann man DNS abfangen?

Auch hier kann es mehrere Ansätze geben.

  • Abfangen des DNS-Verkehrs über PCAP oder NFLOG
    Beide Abfangmethoden sind im Dienstprogramm implementiert sidmat. Aber es wird seit langem nicht mehr unterstützt und die Funktionalität ist sehr primitiv, sodass Sie noch einen Kabelbaum dafür schreiben müssen.
  • Analyse von DNS-Serverprotokollen
    Leider sind die mir bekannten Rekursoren nicht in der Lage, Antworten, sondern nur Anfragen zu protokollieren. Das ist grundsätzlich logisch, da Antworten im Gegensatz zu Bitten eine komplexe Struktur haben und es schwierig ist, sie in Textform zu verfassen.
  • DNSTap
    Glücklicherweise unterstützen viele von ihnen DNSTap für diesen Zweck bereits.

Was ist DNSTap?

Umgehen Sie die ILV-Blockierung mit DNSTap und BGP

Es handelt sich um ein Client-Server-Protokoll, das auf Protokollpuffern und Frame-Streams für die Übertragung strukturierter DNS-Anfragen und -Antworten von einem DNS-Server an einen Sammler basiert. Im Wesentlichen überträgt der DNS-Server Abfrage- und Antwortmetadaten (Nachrichtentyp, Client-/Server-IP usw.) sowie vollständige DNS-Nachrichten in der (binären) Form, in der er mit ihnen über das Netzwerk arbeitet.

Es ist wichtig zu verstehen, dass im DNSTap-Paradigma der DNS-Server als Client und der Collector als Server fungiert. Das heißt, der DNS-Server stellt eine Verbindung zum Collector her und nicht umgekehrt.

Heute wird DNSTap von allen gängigen DNS-Servern unterstützt. Aber beispielsweise wird BIND in vielen Distributionen (wie Ubuntu LTS) aus irgendeinem Grund oft ohne seine Unterstützung erstellt. Kümmern wir uns also nicht um den Zusammenbau, sondern nehmen einen leichteren und schnelleren Rekursor – Unbound.

Wie fängt man DNSTap ein?

Es gibt einige Menge CLI-Dienstprogramme zum Arbeiten mit einem Stream von DNSTap-Ereignissen, aber sie sind nicht zur Lösung unseres Problems geeignet. Deshalb habe ich beschlossen, mein eigenes Fahrrad zu erfinden, das alles Notwendige kann: dnstap-bgp

Arbeitsalgorithmus:

  • Beim Start lädt es eine Liste von Domänen aus einer Textdatei, invertiert sie (habr.com -> com.habr) und schließt unterbrochene Linien, Duplikate und Subdomänen aus (d. h. wenn die Liste habr.com und www.habr.com enthält, es wird nur das erste geladen) und erstellt einen Präfixbaum zum schnellen Durchsuchen dieser Liste
  • Als DNSTap-Server wartet er auf eine Verbindung von einem DNS-Server. Im Prinzip werden sowohl UNIX- als auch TCP-Sockets unterstützt, die mir bekannten DNS-Server können jedoch nur UNIX-Sockets verwenden
  • Eingehende DNSTap-Pakete werden zunächst in eine Protobuf-Struktur deserialisiert, und dann wird die binäre DNS-Nachricht selbst, die sich in einem der Protobuf-Felder befindet, auf die Ebene der DNS-RR-Einträge analysiert
  • Es wird überprüft, ob der angeforderte Host (oder seine übergeordnete Domäne) in der geladenen Liste enthalten ist. Wenn nicht, wird die Antwort ignoriert
  • Aus der Antwort werden nur A/AAAA/CNAME-RRs ausgewählt und die entsprechenden IPv4/IPv6-Adressen daraus extrahiert
  • IP-Adressen werden mit konfigurierbarer TTL zwischengespeichert und allen konfigurierten BGP-Peers bekannt gegeben
  • Beim Empfang einer Antwort, die auf eine bereits zwischengespeicherte IP verweist, wird deren TTL aktualisiert
  • Nach Ablauf der TTL wird der Eintrag aus dem Cache und aus BGP-Ankündigungen entfernt

Zusätzliche Funktionalität:

  • Erneutes Lesen der Domainliste von SIGHUP
  • Den Cache mit anderen Instanzen synchron halten dnstap-bgp über HTTP/JSON
  • Duplizieren Sie den Cache auf der Festplatte (in der BoltDB-Datenbank), um seinen Inhalt nach einem Neustart wiederherzustellen
  • Unterstützung für den Wechsel zu einem anderen Netzwerk-Namespace (warum dies erforderlich ist, wird unten beschrieben)
  • IPv6-Unterstützung

Einschränkungen:

  • IDN-Domänen werden noch nicht unterstützt
  • Wenige BGP-Einstellungen

Ich habe gesammelt RPM und DEB Pakete für eine einfache Installation. Sollte auf allen relativ neuen Betriebssystemen mit systemd funktionieren. Sie haben keine Abhängigkeiten.

Fahren

Beginnen wir also mit dem Zusammenbau aller Komponenten. Als Ergebnis sollten wir etwa diese Netzwerktopologie erhalten:
Umgehen Sie die ILV-Blockierung mit DNSTap und BGP

Die Logik der Arbeit geht meiner Meinung nach aus dem Diagramm hervor:

  • Der Client hat unseren Server als DNS konfiguriert und DNS-Anfragen müssen ebenfalls über das VPN erfolgen. Dies ist notwendig, damit der Provider nicht durch DNS-Abfangen blockieren kann.
  • Beim Öffnen der Site sendet der Client eine DNS-Anfrage wie „Wie lauten die IPs von xxx.org?“
  • Nicht gebunden löst xxx.org auf (oder nimmt es aus dem Cache) und sendet eine Antwort an den Client „xxx.org hat diese und jene IP“ und dupliziert sie parallel über DNSTap
  • dnstap-bgp gibt diese Adressen bekannt Vogel über BGP, wenn die Domain auf der Sperrliste steht
  • Vogel kündigt eine Route zu diesen IPs mit an next-hop self Client-Router
  • Nachfolgende Pakete vom Client an diese IPs durchlaufen den Tunnel

Auf dem Server verwende ich für Routen zu blockierten Sites eine separate Tabelle in BIRD, die in keiner Weise mit dem Betriebssystem überschneidet.

Dieses Schema hat einen Nachteil: Das erste SYN-Paket vom Client wird höchstwahrscheinlich Zeit haben, über den inländischen Anbieter zu gelangen. Die Route wird nicht sofort bekannt gegeben. Und hier sind Optionen möglich, je nachdem, wie der Anbieter die Sperrung vornimmt. Wenn er einfach den Verkehr reduziert, gibt es kein Problem. Und wenn er es auf einige DPI umleitet, sind (theoretisch) Spezialeffekte möglich.

Es ist auch möglich, dass Clients die DNS-TTL-Wunder nicht respektieren, was dazu führen kann, dass der Client einige veraltete Einträge aus seinem faulen Cache verwendet, anstatt Unbound anzufordern.

In der Praxis bereiteten mir weder das erste noch das zweite Probleme, aber Ihr Kilometerstand kann variieren.

Server-Tuning

Um das Rollen zu erleichtern, habe ich geschrieben Rolle für Ansible. Es kann sowohl Server als auch Clients auf Basis von Linux konfigurieren (entworfen für Deb-basierte Distributionen). Alle Einstellungen liegen auf der Hand und sind voreingestellt inventory.yml. Diese Rolle stammt aus meinem großen Playbook und kann daher Fehler enthalten. Anfragen ziehen Willkommen :)

Gehen wir die Hauptkomponenten durch.

BGP

Das Ausführen von zwei BGP-Daemons auf demselben Host hat ein grundlegendes Problem: BIRD möchte kein BGP-Peering mit dem Localhost (oder einer anderen lokalen Schnittstelle) einrichten. Vom Wort her überhaupt. Googeln und Mailinglisten haben nicht geholfen, sie behaupten, dass dies beabsichtigt sei. Vielleicht gibt es eine Möglichkeit, aber ich habe sie nicht gefunden.

Sie können einen anderen BGP-Daemon ausprobieren, aber ich mag BIRD und er wird von mir überall verwendet, ich möchte keine Entitäten produzieren.

Deshalb habe ich dnstap-bgp im Netzwerk-Namespace versteckt, der über die Veth-Schnittstelle mit dem Root verbunden ist: Es ist wie eine Pipe, deren Enden in verschiedene Namespaces hineinragen. An jedem dieser Enden hängen wir private P2P-IP-Adressen, die nicht über den Host hinausgehen, sodass sie alles sein können. Dies ist derselbe Mechanismus, der für den Zugriff auf Prozesse im Inneren verwendet wird von allen geliebt Docker und andere Container.

Dafür wurde es geschrieben Skript und die oben bereits beschriebene Funktionalität, sich an den Haaren in einen anderen Namespace zu ziehen, wurde zu dnstap-bgp hinzugefügt. Aus diesem Grund muss es als Root ausgeführt oder über den Befehl setcap an die CAP_SYS_ADMIN-Binärdatei ausgegeben werden.

Beispielskript zum Erstellen eines 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",
]

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

Standardmäßig wird in Ubuntu die Unbound-Binärdatei durch das AppArmor-Profil eingeschränkt, was ihr die Verbindung zu allen Arten von DNSTap-Sockets verbietet. Sie können dieses Profil entweder löschen oder deaktivieren:

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

Dies sollte wahrscheinlich zum Playbook hinzugefügt werden. Ideal ist es natürlich, das Profil zu korrigieren und die nötigen Rechte zu vergeben, aber ich war zu faul.

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

Listen herunterladen und bearbeiten

Skript zum Herunterladen und Verarbeiten einer Liste von IP-Adressen
Es lädt die Liste herunter und fasst sie zum Präfix zusammen pfx. In dont_add и dont_summarize Sie können den IPs und Netzwerken mitteilen, dass sie übersprungen oder nicht zusammengefasst werden sollen. Ich brauchte es. das Subnetz meines VPS war in der Blocklist 🙂

Das Lustige ist, dass die RosKomSvoboda-API Anfragen mit dem Standard-Python-Benutzeragenten blockiert. Sieht so aus, als ob der Drehbuch-Kiddy es verstanden hat. Deshalb ändern wir es in Ognelis.

Bisher funktioniert es nur mit IPv4. Der Anteil von IPv6 ist gering, lässt sich aber leicht beheben. Es sei denn, Sie müssen auch Bird6 verwenden.

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)

Skript zum Aktualisieren
Ich lasse es einmal am Tag auf der Krone laufen, vielleicht lohnt es sich, es alle 4 Stunden zu ziehen. Dies ist meiner Meinung nach die Verlängerungsfrist, die das RKN von den Anbietern verlangt. Außerdem verfügen sie über einige andere sehr dringende Sperrungen, die möglicherweise schneller eintreffen.

Führt Folgendes aus:

  • Führt das erste Skript aus und aktualisiert die Liste der Routen (rkn_routes.list) für VOGEL
  • BIRD neu laden
  • Aktualisiert und bereinigt die Liste der Domänen für dnstap-bgp
  • Laden Sie dnstap-bgp neu

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

Sie wurden ohne viel Nachdenken geschrieben. Wenn Sie also etwas sehen, das verbessert werden kann, dann tun Sie es.

Client-Setup

Hier gebe ich Beispiele für Linux-Router, aber im Fall von Mikrotik / Cisco dürfte es noch einfacher sein.

Zuerst richten wir BIRD ein:

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

Daher synchronisieren wir die von BGP empfangenen Routen mit der Kernel-Routing-Tabelle Nummer 222.

Danach reicht es aus, den Kernel zu bitten, sich diese Platte anzusehen, bevor er sich die Standardplatte ansieht:

# 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

Alles, es bleibt noch, DHCP auf dem Router zu konfigurieren, um die Tunnel-IP-Adresse des Servers als DNS zu verteilen, und das Schema ist fertig.

Begrenztheit

Mit dem aktuellen Algorithmus zur Generierung und Verarbeitung der Domainliste umfasst dieser unter anderem: youtube.com und seine CDNs.

Und das führt dazu, dass alle Videos über das VPN laufen, was den gesamten Kanal verstopfen kann. Vielleicht lohnt es sich, eine Liste beliebter Domain-Ausschlüsse zusammenzustellen, die das RKN vorerst blockieren, der Mut ist dünn. Und überspringen Sie sie beim Parsen.

Abschluss

Mit der beschriebenen Methode können Sie nahezu alle Blockierungen umgehen, die derzeit von Anbietern implementiert werden.

Prinzipiell dnstap-bgp kann für jeden anderen Zweck verwendet werden, bei dem ein gewisses Maß an Verkehrskontrolle basierend auf dem Domänennamen erforderlich ist. Bedenken Sie jedoch, dass heutzutage tausend Websites an derselben IP-Adresse hängen können (z. B. hinter einigen Cloudflare-Websites), sodass diese Methode eine eher geringe Genauigkeit aufweist.

Aber für die Bedürfnisse der Umgehung von Sperren reicht das völlig aus.

Ergänzungen, Änderungen, Pull-Requests – willkommen!

Source: habr.com

Kommentar hinzufügen