Omiń blokowanie ILV za pomocą DNSTap i BGP

Omiń blokowanie ILV za pomocą DNSTap i BGP

Temat jest już mocno wyczerpany, wiem. Na przykład jest świetny artykuł, ale uwzględniana jest tam tylko część dotycząca adresów IP listy zablokowanych. Dodamy również domeny.

W związku z tym, że sądy i RKN blokują wszystko na prawo i lewo, a dostawcy usilnie starają się nie narażać na kary wydawane przez Revizorro, straty z tym związane są dość duże. A wśród „prawnie” zablokowanych witryn jest wiele przydatnych (cześć, rutracker)

Mieszkam poza jurysdykcją RKN, ale moi rodzice, krewni i przyjaciele pozostali w domu. Postanowiono więc wymyślić prosty sposób na obejście blokowania przez osoby z dala od IT, najlepiej bez ich udziału.

W tej notatce nie będę opisywał w krokach podstawowych rzeczy sieciowych, ale opiszę ogólne zasady, jak ten schemat można wdrożyć. Dlatego wiedza o tym, jak ogólnie działa sieć, aw szczególności w Linuksie, jest koniecznością.

Rodzaje zamków

Najpierw odświeżmy naszą pamięć o tym, co jest blokowane.

Istnieje kilka rodzajów blokad w rozładowanym pliku XML z RKN:

  • IP
  • Домен
  • URL

Dla uproszczenia zredukujemy je do dwóch: IP i domena, a domenę po prostu wyciągniemy z blokowania po adresie URL (dokładniej już to za nas zrobili).

dobrzy ludzie z Roskomswoboda zrealizował cud API, dzięki któremu możemy uzyskać to, czego potrzebujemy:

Dostęp do zablokowanych stron

Aby to zrobić, potrzebujemy jakiegoś małego zagranicznego VPS-a, najlepiej z nieograniczonym ruchem - jest ich wiele za 3-5 dolców. Musisz wziąć to w bliskiej zagranicy, aby ping nie był bardzo duży, ale znowu weź pod uwagę, że Internet i geografia nie zawsze się pokrywają. A ponieważ nie ma umowy SLA za 5 dolców, lepiej wziąć 2+ sztuki od różnych dostawców, aby zapewnić odporność na awarie.

Następnie musimy skonfigurować zaszyfrowany tunel z routera klienta do VPS. Używam Wireguard jako najszybszego i najłatwiejszego w konfiguracji. Mam również routery klienckie oparte na systemie Linux (APU2 lub coś w OpenWRT). W przypadku niektórych Mikrotik/Cisco można skorzystać z dostępnych na nich protokołów typu OpenVPN oraz GRE-over-IPSEC.

Identyfikacja i przekierowanie ruchu będącego przedmiotem zainteresowania

Możesz oczywiście wyłączyć cały ruch internetowy przez inne kraje. Ale najprawdopodobniej znacznie ucierpi na tym szybkość pracy z treściami lokalnymi. Ponadto wymagania dotyczące przepustowości na VPS będą znacznie wyższe.

Dlatego będziemy musieli jakoś przydzielić ruch do zablokowanych stron i selektywnie skierować go do tunelu. Nawet jeśli dotrze tam część „dodatkowego” ruchu, i tak jest to o wiele lepsze niż przejechanie wszystkiego przez tunel.

Do zarządzania ruchem będziemy używać protokołu BGP i ogłaszać klientom trasy do niezbędnych sieci z naszego VPS. Weźmy BIRD jako jeden z najbardziej funkcjonalnych i wygodnych demonów BGP.

IP

Dzięki blokowaniu przez adres IP wszystko jest jasne: po prostu ogłaszamy wszystkie zablokowane adresy IP za pomocą VPS. Problem polega na tym, że na liście zwracanej przez API znajduje się około 600 tysięcy podsieci, a zdecydowana większość z nich to hosty /32. Ta liczba tras może zmylić słabe routery klienckie.

Dlatego podczas przetwarzania listy zdecydowano się na podsumowanie do sieci /24, jeśli ma ona 2 lub więcej hostów. Tym samym liczba tras została zmniejszona do ~100 tys. Skrypt do tego nastąpi.

Domeny

Jest to bardziej skomplikowane i istnieje kilka sposobów. Na przykład możesz zainstalować przezroczystego Squida na każdym routerze klienckim i tam przechwycić HTTP i zajrzeć do uzgadniania TLS, aby uzyskać żądany adres URL w pierwszym przypadku i domenę z SNI w drugim.

Ale ze względu na wszelkiego rodzaju nowomodne TLS1.3 + eSNI analiza HTTPS staje się z każdym dniem coraz mniej realna. Tak, a infrastruktura po stronie klienta staje się coraz bardziej skomplikowana - będziesz musiał użyć co najmniej OpenWRT.

Dlatego zdecydowałem się pójść drogą przechwytywania odpowiedzi na zapytania DNS. Tutaj też każdy DNS-over-TLS/HTTPS zaczyna unosić się nad twoją głową, ale możemy (na razie) kontrolować tę część na kliencie – albo ją wyłącz, albo użyj własnego serwera dla DoT/DoH.

Jak przechwycić DNS?

Tutaj również może być kilka podejść.

  • Przechwytywanie ruchu DNS przez PCAP lub NFLOG
    Obie te metody przechwytywania są zaimplementowane w narzędziu sidmat. Ale od dawna nie jest wspierany, a funkcjonalność jest bardzo prymitywna, więc nadal musisz napisać dla niego uprząż.
  • Analiza logów serwera DNS
    Niestety znane mi rekursory nie są w stanie logować odpowiedzi, a jedynie żądania. Zasadniczo jest to logiczne, ponieważ w przeciwieństwie do próśb odpowiedzi mają złożoną strukturę i trudno jest je zapisać w formie tekstowej.
  • DNSDotknij
    Na szczęście wiele z nich obsługuje już w tym celu DNSTap.

Co to jest DNSTap?

Omiń blokowanie ILV za pomocą DNSTap i BGP

Jest to protokół klient-serwer oparty na buforach protokołów i strumieniach ramek do przesyłania z serwera DNS do kolektora ustrukturyzowanych zapytań i odpowiedzi DNS. Zasadniczo serwer DNS przesyła metadane zapytań i odpowiedzi (rodzaj wiadomości, adres IP klienta/serwera itp.) oraz kompletne wiadomości DNS w formie (binarnej), w której pracuje z nimi przez sieć.

Ważne jest, aby zrozumieć, że w paradygmacie DNSTap serwer DNS działa jako klient, a kolektor działa jako serwer. Oznacza to, że serwer DNS łączy się z kolektorem, a nie odwrotnie.

Dzisiaj DNSTap jest obsługiwany we wszystkich popularnych serwerach DNS. Ale na przykład BIND w wielu dystrybucjach (takich jak Ubuntu LTS) jest często budowany z jakiegoś powodu bez jego obsługi. Więc nie zawracajmy sobie głowy ponownym składaniem, ale weźmy lżejszy i szybszy rekursor - Unbound.

Jak złapać DNSTap?

Jest Niektóre liczba Narzędzia CLI do pracy ze strumieniem zdarzeń DNSTap, ale nie nadają się do rozwiązania naszego problemu. Dlatego postanowiłem wymyślić własny rower, który zrobi wszystko, co konieczne: dnstap-bgp

Algorytm pracy:

  • Po uruchomieniu ładuje listę domen z pliku tekstowego, odwraca je (habr.com -> com.habr), wyklucza łamane linie, duplikaty i subdomeny (tzn. jeśli lista zawiera habr.com i www.habr.com, zostanie załadowany tylko pierwszy) i zbuduje drzewo prefiksów do szybkiego przeszukiwania tej listy
  • Działając jako serwer DNSTap, oczekuje na połączenie z serwera DNS. Zasadniczo obsługuje zarówno gniazda UNIX, jak i TCP, ale znane mi serwery DNS mogą używać tylko gniazd UNIX
  • Przychodzące pakiety DNSTap są najpierw deserializowane do struktury Protobuf, a następnie sam binarny komunikat DNS znajdujący się w jednym z pól Protobuf jest analizowany do poziomu rekordów DNS RR
  • Sprawdzane jest, czy żądany host (lub jego domena nadrzędna) znajduje się na załadowanej liście, jeśli nie, odpowiedź jest ignorowana
  • Z odpowiedzi wybierane są tylko rekordy RR A/AAAA/CNAME, z których wyodrębniane są odpowiednie adresy IPv4/IPv6
  • Adresy IP są buforowane z konfigurowalnym TTL i ogłaszane wszystkim skonfigurowanym peerom BGP
  • Po otrzymaniu odpowiedzi wskazującej na już buforowany adres IP, jego TTL jest aktualizowany
  • Po wygaśnięciu TTL wpis jest usuwany z pamięci podręcznej i ogłoszeń BGP

Dodatkowa funkcjonalność:

  • Ponowne odczytanie listy domen przez SIGHUP
  • Utrzymywanie synchronizacji pamięci podręcznej z innymi instancjami dnstap-bgp przez HTTP/JSON
  • Zduplikuj pamięć podręczną na dysku (w bazie danych BoltDB), aby przywrócić jej zawartość po restarcie
  • Obsługa przełączania do innej sieciowej przestrzeni nazw (dlaczego jest to potrzebne zostanie opisane poniżej)
  • Obsługa IPv6

Ograniczenia:

  • Domeny IDN nie są jeszcze obsługiwane
  • Kilka ustawień BGP

zebrałem RPM i DEB pakiety ułatwiające instalację. Powinien działać na wszystkich stosunkowo nowych systemach operacyjnych z systemd. nie mają żadnych zależności.

Schemat

Zacznijmy więc montować wszystkie elementy razem. W rezultacie powinniśmy otrzymać coś w rodzaju tej topologii sieci:
Omiń blokowanie ILV za pomocą DNSTap i BGP

Myślę, że logika pracy jest jasna ze schematu:

  • Klient ma skonfigurowany nasz serwer jako DNS, a zapytania DNS muszą również przechodzić przez VPN. Jest to konieczne, aby dostawca nie mógł blokować przechwytywania DNS.
  • Podczas otwierania witryny klient wysyła zapytanie DNS, takie jak „jakie są adresy IP xxx.org”
  • Rozwiązany rozwiązuje xxx.org (lub pobiera go z pamięci podręcznej) i wysyła odpowiedź do klienta „xxx.org ma takie a takie IP”, duplikując go równolegle przez DNSTap
  • dnstap-bgp ogłasza te adresy w BIRD przez BGP, jeśli domena znajduje się na liście zablokowanych
  • BIRD ogłasza trasę do tych adresów IP za pomocą next-hop self router klienta
  • Kolejne pakiety od klienta do tych adresów IP przechodzą przez tunel

Na serwerze do tras do zablokowanych stron używam osobnej tabeli wewnątrz BIRD-a i nie koliduje ona w żaden sposób z systemem operacyjnym.

W tym schemacie jest wada: pierwszy pakiet SYN od klienta najprawdopodobniej będzie miał czas na opuszczenie przez dostawcę krajowego. trasa nie jest ogłaszana od razu. I tutaj możliwe są opcje w zależności od tego, jak dostawca wykonuje blokowanie. Jeśli po prostu ograniczy ruch, nie ma problemu. A jeśli przekieruje to do jakiegoś DPI, to (teoretycznie) efekty specjalne są możliwe.

Możliwe jest również, że klienci nie respektują cudów DNS TTL, co może spowodować, że klient użyje niektórych nieaktualnych wpisów ze swojej zgniłej pamięci podręcznej zamiast prosić o Unbound.

W praktyce ani pierwszy, ani drugi nie sprawiał mi problemów, ale Twój przebieg może się różnić.

Strojenie serwera

Dla ułatwienia toczenia napisałem rola dla Ansible. Może konfigurować zarówno serwery, jak i klientów opartych na systemie Linux (przeznaczonym do dystrybucji opartych na deb). Wszystkie ustawienia są dość oczywiste i są ustawione inwentarz.yml. Ta rola została wycięta z mojego dużego podręcznika, więc może zawierać błędy - żądania ściągania zapraszamy 🙂

Przejdźmy przez główne komponenty.

BGP

Uruchamianie dwóch demonów BGP na tym samym hoście ma fundamentalny problem: BIRD nie chce skonfigurować komunikacji równorzędnej BGP z hostem lokalnym (lub jakimkolwiek interfejsem lokalnym). Od słowa w ogóle. Googlowanie i czytanie list mailingowych nie pomogło, twierdzą, że jest to zgodne z projektem. Być może jest jakiś sposób, ale go nie znalazłem.

Możesz spróbować innego demona BGP, ale ja lubię BIRD-a i wszędzie go używam, nie chcę tworzyć bytów.

Dlatego ukryłem dnstap-bgp w przestrzeni nazw sieci, która jest połączona z rootem przez interfejs veth: jest jak rura, której końce wystają w różnych przestrzeniach nazw. Na każdym z tych końców zawieszamy prywatne adresy IP p2p, które nie wychodzą poza hosta, więc mogą być dowolne. Jest to ten sam mechanizm, który służy do uzyskiwania dostępu do procesów wewnątrz kochany przez wszystkich Docker i inne kontenery.

W tym celu zostało napisane scenariusz a opisana powyżej funkcjonalność przeciągania się za włosy do innej przestrzeni nazw została dodana do dnstap-bgp. Z tego powodu musi być uruchamiany jako root lub wydany do pliku binarnego CAP_SYS_ADMIN za pomocą komendy setcap.

Przykładowy skrypt do tworzenia przestrzeni nazw

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

ptak.konf

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

Domyślnie w Ubuntu plik binarny Unbound jest blokowany przez profil AppArmor, który zabrania mu łączenia się z wszelkiego rodzaju gniazdami DNSTap. Możesz usunąć ten profil lub go wyłączyć:

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

To chyba powinno być dodane do playbooka. Idealnie byłoby oczywiście poprawić profil i wydać niezbędne uprawnienia, ale byłem zbyt leniwy.

niezwiązany.konf

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

Pobieranie i przetwarzanie list

Skrypt do pobierania i przetwarzania listy adresów IP
Pobiera listę, podsumowuje do prefiksu pff, nie dodawaj и nie_podsumowuj możesz powiedzieć adresom IP i sieciom, aby pominęły lub nie podsumowały. Potrzebowałam tego. podsieć mojego VPS była na liście zablokowanych 🙂

Zabawne jest to, że API RosKomSvoboda blokuje żądania za pomocą domyślnego agenta użytkownika Pythona. Wygląda na to, że scenarzysta to zrozumiał. Dlatego zmieniamy go na Ognelis.

Jak dotąd działa tylko z IPv4. udział IPv6 jest niewielki, ale będzie łatwy do naprawienia. Chyba że musisz również użyć 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)

Skrypt do aktualizacji
Nakładam go na koronę raz dziennie, może warto podciągać co 4 godziny. moim zdaniem jest to okres odnowienia, którego RKN wymaga od dostawców. Ponadto mają inne bardzo pilne blokowanie, które może nadejść szybciej.

Czy:

  • Uruchamia pierwszy skrypt i aktualizuje listę tras (rkn_routes.list) dla PTAKA
  • Załaduj ponownie BIRD'a
  • Aktualizuje i czyści listę domen dla dnstap-bgp
  • Załaduj ponownie 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

Zostały napisane bez większego namysłu, więc jeśli widzisz coś, co można poprawić - zrób to.

Konfiguracja klienta

Tutaj podam przykłady dla routerów z Linuksem, ale w przypadku Mikrotika/Cisco powinno być jeszcze łatwiej.

Najpierw konfigurujemy BIRD:

ptak.konf

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

W ten sposób zsynchronizujemy trasy otrzymane z BGP z tablicą routingu jądra o numerze 222.

Następnie wystarczy poprosić jądro, aby spojrzało na tę tabliczkę, zanim spojrzy na domyślną:

# 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

Wszystko, pozostaje skonfigurować DHCP na routerze do dystrybucji adresu IP tunelu serwera jako DNS i schemat jest gotowy.

Ograniczenia

Przy obecnym algorytmie generowania i przetwarzania listy domen zawiera ona m.in. youtube.com i jego sieci CDN.

A to prowadzi do tego, że wszystkie filmy przejdą przez VPN, co może zatkać cały kanał. Być może warto sporządzić listę popularnych domen-wykluczeń, które na razie blokują RKN, wnętrzności są cienkie. I pomiń je podczas analizowania.

wniosek

Opisana metoda pozwala ominąć prawie wszystkie blokady, które obecnie wdrażają dostawcy.

W zasadzie, dnstap-bgp może być używany do dowolnego innego celu, w którym wymagany jest pewien poziom kontroli ruchu w oparciu o nazwę domeny. Pamiętaj tylko, że w naszych czasach tysiące witryn może zawiesić się na tym samym adresie IP (na przykład za jakimś Cloudflare), więc ta metoda ma raczej niską dokładność.

Ale na potrzeby ominięcia zamków to wystarczy.

Dodatki, edycje, pull requesty - zapraszamy!

Źródło: www.habr.com

Dodaj komentarz