Omgå ILV-blokkering med DNSTap og BGP

Omgå ILV-blokkering med DNSTap og BGP

Temaet er ganske banket opp, jeg vet. For eksempel er det en flott artikkel, men bare IP-delen av blokkeringslisten vurderes der. Vi vil også legge til domener.

På grunn av det faktum at domstolene og RKN blokkerer alt til høyre og venstre, og leverandørene prøver hardt å ikke falle inn under bøtene utstedt av Revizorro, er de tilhørende tapene fra blokkering ganske store. Og blant de "lovlig" blokkerte nettstedene er det mange nyttige (hei, rutracker)

Jeg bor utenfor RKNs jurisdiksjon, men mine foreldre, slektninger og venner forble hjemme. Så det ble besluttet å komme opp med en enkel måte for folk langt fra IT å omgå blokkering, helst uten deres deltagelse i det hele tatt.

I dette notatet vil jeg ikke beskrive de grunnleggende nettverkstingene i trinn, men jeg vil beskrive de generelle prinsippene for hvordan denne ordningen kan implementeres. Så kunnskap om hvordan nettverket fungerer generelt og i Linux spesielt er et must.

Typer låser

Først, la oss friske opp minnet om hva som blir blokkert.

Det er flere typer låser i den utlastede XML-en fra RKN:

  • IP
  • Domenenavn
  • URL

For enkelhets skyld vil vi redusere dem til to: IP og domene, og vi vil ganske enkelt trekke ut domenet fra blokkering av URL (mer presist, de har allerede gjort dette for oss).

gode folk fra Roskomsvoboda innså en fantastisk API, der vi kan få det vi trenger:

Tilgang til blokkerte nettsteder

For å gjøre dette trenger vi noen små utenlandske VPS, gjerne med ubegrenset trafikk – det finnes mange av disse for 3-5 spenn. Du må ta det i nærheten av utlandet slik at ping ikke er veldig stort, men igjen, ta hensyn til at Internett og geografi ikke alltid faller sammen. Og siden det ikke er noen SLA for 5 dollar, er det bedre å ta 2+ stykker fra forskjellige leverandører for feiltoleranse.

Deretter må vi sette opp en kryptert tunnel fra klientruteren til VPS. Jeg bruker Wireguard som den raskeste og enkleste å sette opp. Jeg har også klientrutere basert på Linux (APU2 eller noe i OpenWRT). Når det gjelder noen Mikrotik / Cisco, kan du bruke protokollene som er tilgjengelige på dem som OpenVPN og GRE-over-IPSEC.

Identifisering og omdirigering av interessetrafikk

Du kan selvfølgelig slå av all Internett-trafikk gjennom utlandet. Men mest sannsynlig vil hastigheten på arbeidet med lokalt innhold lide sterkt av dette. I tillegg vil båndbreddekravene på VPS være mye høyere.

Derfor må vi på en eller annen måte allokere trafikk til blokkerte nettsteder og selektivt dirigere den til tunnelen. Selv om noe av "ekstra" trafikken kommer dit, er det likevel mye bedre enn å kjøre alt gjennom tunnelen.

For å administrere trafikk vil vi bruke BGP-protokollen og kunngjøre ruter til nødvendige nettverk fra vår VPS til klienter. La oss ta BIRD som en av de mest funksjonelle og praktiske BGP-demonene.

IP

Med blokkering av IP er alt klart: vi kunngjør ganske enkelt alle blokkerte IP-er med VPS. Problemet er at det er omtrent 600 tusen subnett i listen som API-en returnerer, og de aller fleste av dem er /32-verter. Dette antallet ruter kan forvirre svake klientrutere.

Derfor, under behandlingen av listen, ble det besluttet å oppsummere opp til nettverket / 24 hvis det har 2 eller flere verter. Dermed ble antall ruter redusert til ~100 tusen. Manuset til dette vil følge.

domener

Det er mer komplisert og det er flere måter. For eksempel kan du installere en transparent Squid på hver klientruter og utføre HTTP-avskjæring der og titte inn i TLS-håndtrykket for å få den forespurte URL-en i det første tilfellet og domenet fra SNI i det andre.

Men på grunn av alle slags nymotens TLS1.3 + eSNI, blir HTTPS-analyse mindre og mindre reell for hver dag. Ja, og infrastrukturen på klientsiden blir mer komplisert - du må bruke minst OpenWRT.

Derfor bestemte jeg meg for å ta veien for å avskjære svar på DNS-forespørsler. Også her begynner enhver DNS-over-TLS / HTTPS å sveve over hodet ditt, men vi kan (foreløpig) kontrollere denne delen på klienten - enten deaktivere den eller bruke din egen server for DoT / DoH.

Hvordan avskjære DNS?

Også her kan det være flere tilnærminger.

  • Avlytting av DNS-trafikk via PCAP eller NFLOG
    Begge disse metodene for avskjæring er implementert i verktøyet sidmat. Men det har ikke vært støttet på lenge, og funksjonaliteten er veldig primitiv, så du må fortsatt skrive en sele for den.
  • Analyse av DNS-serverlogger
    Dessverre klarer ikke gjentakerne jeg kjenner til å logge svar, men kun forespørsler. I prinsippet er dette logisk, siden svar, i motsetning til forespørsler, har en kompleks struktur og det er vanskelig å skrive dem i tekstform.
  • DNSTap
    Heldigvis støtter mange av dem allerede DNSTap for dette formålet.

Hva er DNSTap?

Omgå ILV-blokkering med DNSTap og BGP

Det er en klient-server-protokoll basert på protokollbuffere og rammestrømmer for overføring fra en DNS-server til en samler av strukturerte DNS-spørringer og -svar. I hovedsak overfører DNS-serveren spørrings- og svarmetadata (meldingstype, klient/server-IP, etc.) pluss komplette DNS-meldinger i den (binære) formen den fungerer med dem over nettverket.

Det er viktig å forstå at i DNSTap-paradigmet fungerer DNS-serveren som en klient og innsamleren fungerer som en server. Det vil si at DNS-serveren kobler til samleren, og ikke omvendt.

I dag støttes DNSTap i alle populære DNS-servere. Men, for eksempel, er BIND i mange distribusjoner (som Ubuntu LTS) ofte bygget av en eller annen grunn uten støtte. Så la oss ikke bry oss med remontering, men ta en lettere og raskere rekursor – Ubundet.

Hvordan fange DNSTap?

Det er noen nummer CLI-verktøy for å jobbe med en strøm av DNSTap-hendelser, men de er ikke egnet for å løse problemet vårt. Derfor bestemte jeg meg for å finne opp min egen sykkel som vil gjøre alt som er nødvendig: dnstap-bgp

Arbeidsalgoritme:

  • Når den lanseres, laster den inn en liste over domener fra en tekstfil, inverterer dem (habr.com -> com.habr), ekskluderer brutte linjer, duplikater og underdomener (dvs. hvis listen inneholder habr.com og www.habr.com, den vil bare lastes inn den første) og bygger et prefiksetre for rask søking gjennom denne listen
  • Fungerer som en DNSTap-server og venter på en tilkobling fra en DNS-server. I prinsippet støtter den både UNIX- og TCP-sockets, men DNS-serverne jeg kjenner kan kun bruke UNIX-sockets
  • Innkommende DNSTap-pakker blir først deserialisert til en Protobuf-struktur, og deretter blir selve den binære DNS-meldingen, som ligger i et av Protobuf-feltene, analysert til nivået for DNS RR-poster
  • Det kontrolleres om den forespurte verten (eller dens overordnede domene) er i den lastede listen, hvis ikke, ignoreres svaret
  • Bare A/AAAA/CNAME RR-er velges fra svaret, og de tilsvarende IPv4/IPv6-adressene trekkes ut fra dem
  • IP-adresser bufres med konfigurerbar TTL og annonseres til alle konfigurerte BGP-kolleger
  • Når du mottar et svar som peker på en allerede bufret IP, oppdateres TTL
  • Etter at TTL utløper, fjernes oppføringen fra hurtigbufferen og fra BGP-kunngjøringer

Ekstra funksjonalitet:

  • Leser listen over domener av SIGHUP på nytt
  • Holder hurtigbufferen synkronisert med andre forekomster dnstap-bgp via HTTP/JSON
  • Dupliser cachen på disken (i BoltDB-databasen) for å gjenopprette innholdet etter en omstart
  • Støtte for å bytte til et annet nettverksnavnområde (hvorfor dette er nødvendig vil bli beskrevet nedenfor)
  • IPv6-støtte

restriksjoner:

  • IDN-domener støttes ikke ennå
  • Få BGP-innstillinger

jeg samlet RPM og DEB pakker for enkel installasjon. Bør fungere på alle relativt nye OSer med systemd. de har ingen avhengigheter.

Ordningen

Så la oss begynne å sette sammen alle komponentene. Som et resultat bør vi få noe sånt som denne nettverkstopologien:
Omgå ILV-blokkering med DNSTap og BGP

Arbeidslogikken tror jeg er tydelig fra diagrammet:

  • Klienten har serveren vår konfigurert som DNS, og DNS-spørringer må også gå over VPN. Dette er nødvendig for at leverandøren ikke skal kunne bruke DNS-avlytting til å blokkere.
  • Når du åpner nettstedet, sender klienten en DNS-spørring som "hva er IP-ene til xxx.org"
  • ubundet løser xxx.org (eller tar det fra cachen) og sender et svar til klienten "xxx.org har en slik og slik IP", dupliserer den parallelt via DNSTap
  • dnstap-bgp kunngjør disse adressene i FUGL via BGP hvis domenet er på blokkeringslisten
  • FUGL annonserer en rute til disse IP-ene med next-hop self klientruter
  • Påfølgende pakker fra klienten til disse IP-ene går gjennom tunnelen

På serveren, for ruter til blokkerte nettsteder, bruker jeg en egen tabell inne i BIRD, og ​​den krysser ikke OS på noen måte.

Denne ordningen har en ulempe: den første SYN-pakken fra klienten vil mest sannsynlig ha tid til å forlate den innenlandske leverandøren. ruten annonseres ikke umiddelbart. Og her er alternativer mulige avhengig av hvordan leverandøren blokkerer. Hvis han bare slipper trafikken, så er det ikke noe problem. Og hvis han omdirigerer det til en eller annen DPI, så er (teoretisk) spesialeffekter mulig.

Det er også mulig at klienter ikke respekterer DNS TTL-mirakler, noe som kan føre til at klienten bruker noen foreldede oppføringer fra den råtne cachen i stedet for å spørre Unbound.

I praksis skapte verken den første eller den andre problemer for meg, men kjørelengden din kan variere.

Server Tuning

For enkelhets skyld skrev jeg rolle for Ansible. Den kan konfigurere både servere og klienter basert på Linux (designet for deb-baserte distribusjoner). Alle innstillinger er ganske åpenbare og er satt inn inventory.yml. Denne rollen er klippet fra min store spillebok, så den kan inneholde feil - trekk forespørsler velkommen 🙂

La oss gå gjennom hovedkomponentene.

bgp

Å kjøre to BGP-demoner på samme vert har et grunnleggende problem: BIRD ønsker ikke å sette opp BGP-peering med den lokale verten (eller noe lokalt grensesnitt). Fra ordet i det hele tatt. Det hjalp ikke å google og lese e-postlister, de hevder at dette er ved design. Kanskje det er en måte, men jeg fant den ikke.

Du kan prøve en annen BGP-demon, men jeg liker BIRD og den brukes overalt av meg, jeg vil ikke produsere enheter.

Derfor gjemte jeg dnstap-bgp inne i nettverksnavneområdet, som er koblet til roten gjennom veth-grensesnittet: det er som et rør, hvis ender stikker ut i forskjellige navnerom. I hver av disse endene henger vi private p2p IP-adresser som ikke går utover verten, så de kan være hva som helst. Dette er den samme mekanismen som brukes for å få tilgang til prosesser inne elsket av alle Docker og andre containere.

For dette ble det skrevet manus og funksjonaliteten som allerede er beskrevet ovenfor for å dra deg selv i håret til et annet navneområde ble lagt til dnstap-bgp. På grunn av dette må den kjøres som root eller gis til CAP_SYS_ADMIN-binæren via setcap-kommandoen.

Eksempelskript for å lage navneområde

#!/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

Som standard, i Ubuntu, er Unbound-binæren klemt av AppArmor-profilen, som forbyr den fra å koble til alle slags DNSTap-sokler. Du kan enten slette denne profilen eller deaktivere den:

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

Dette bør nok legges til spilleboken. Det er selvfølgelig ideelt å korrigere profilen og utstede de nødvendige rettighetene, men jeg var for lat.

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

Nedlasting og behandling av lister

Skript for nedlasting og behandling av en liste over IP-adresser
Den laster ned listen, summerer opp til prefikset pfx. I ikke_legg til и ikke_oppsummere du kan fortelle IP-ene og nettverkene om å hoppe over eller ikke oppsummere. Jeg trengte det. undernettet til min VPS var på blokkeringslisten 🙂

Det morsomme er at RosKomSvoboda API blokkerer forespørsler med standard Python-brukeragent. Ser ut som manus-kiddy har det. Derfor endrer vi den til Ognelis.

Så langt fungerer det kun med IPv4. andelen IPv6 er liten, men det vil være enkelt å fikse. Med mindre du må bruke bird6 også.

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 for å oppdatere
Jeg kjører den på kronen en gang om dagen, kanskje det er verdt å trekke den hver 4. time. dette er etter min mening fornyelsesperioden som RKN krever av tilbydere. I tillegg har de en annen superhastende blokkering, som kan komme raskere.

Gjør følgende:

  • Kjører det første skriptet og oppdaterer listen over ruter (rkn_routes.list) for BIRD
  • Last BIRD på nytt
  • Oppdaterer og rydder opp i listen over domener for dnstap-bgp
  • Last inn dnstap-bgp på nytt

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

De ble skrevet uten mye tankegang, så hvis du ser noe som kan forbedres - gå for det.

Klientoppsett

Her vil jeg gi eksempler for Linux-rutere, men i tilfellet Mikrotik/Cisco skal det være enda enklere.

Først setter vi opp BIRD:

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

Dermed vil vi synkronisere rutene mottatt fra BGP med kjernerutingstabell nummer 222.

Etter det er det nok å be kjernen se på denne platen før du ser på standardplaten:

# 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

Alt, det gjenstår å konfigurere DHCP på ruteren for å distribuere serverens tunnel IP-adresse som DNS, og ordningen er klar.

Begrensninger

Med dagens algoritme for å generere og behandle listen over domener inkluderer den bl.a. youtube.com og dets CDN-er.

Og dette fører til det faktum at alle videoer vil gå gjennom VPN, som kan tette hele kanalen. Kanskje det er verdt å sette sammen en liste over populære domener-ekskluderinger som blokkerer RKN foreløpig, tarmen er tynn. Og hopp over dem når du analyserer.

Konklusjon

Den beskrevne metoden lar deg omgå nesten enhver blokkering som leverandørene implementerer for øyeblikket.

I prinsippet, dnstap-bgp kan brukes til alle andre formål der et visst nivå av trafikkkontroll er nødvendig basert på domenenavnet. Bare husk at i vår tid kan tusen nettsteder henge på samme IP-adresse (bak noen Cloudflare, for eksempel), så denne metoden har en ganske lav nøyaktighet.

Men for behovene til å omgå låser er dette ganske nok.

Tilføyelser, redigeringer, pull-forespørsler - velkommen!

Kilde: www.habr.com

Legg til en kommentar