Förbigå ILV-blockering med DNSTap och BGP

Förbigå ILV-blockering med DNSTap och BGP

Ämnet är ganska upprört, jag vet. Till exempel finns det en stor artikel, men endast IP-delen av blockeringslistan beaktas där. Vi kommer också att lägga till domäner.

På grund av det faktum att domstolarna och RKN blockerar allt till höger och vänster, och leverantörerna försöker hårt för att inte falla under de böter som utfärdats av Revizorro, är de tillhörande förlusterna från blockering ganska stora. Och bland de "lagligt" blockerade webbplatserna finns det många användbara (hej, rutracker)

Jag bor utanför RKN:s jurisdiktion, men mina föräldrar, släktingar och vänner förblev hemma. Så man bestämde sig för att komma på ett enkelt sätt för människor långt ifrån IT att kringgå blockering, helst utan deras deltagande alls.

I denna not kommer jag inte att beskriva de grundläggande nätverkssakerna i steg, utan kommer att beskriva de allmänna principerna för hur detta schema kan implementeras. Så kunskap om hur nätverket fungerar i allmänhet och i Linux i synnerhet är ett måste.

Typer av lås

Låt oss först fräscha upp vårt minne av det som blockeras.

Det finns flera typer av lås i den avlästa XML-filen från RKN:

  • IP
  • Домен
  • URL

För enkelhetens skull kommer vi att reducera dem till två: IP och domän, och vi kommer helt enkelt att ta bort domänen från blockering via URL (mer exakt, de har redan gjort detta åt oss).

bra människor från Roskomsvoboda insåg en underbar API, genom vilken vi kan få det vi behöver:

Tillgång till blockerade webbplatser

För att göra detta behöver vi några små utländska VPS, gärna med obegränsad trafik – det finns många sådana för 3-5 spänn. Du måste ta det i närheten av utlandet så att pingen inte är särskilt stor, men återigen, ta hänsyn till att internet och geografi inte alltid sammanfaller. Och eftersom det inte finns någon SLA för 5 spänn är det bättre att ta 2+ stycken från olika leverantörer för feltolerans.

Därefter måste vi ställa in en krypterad tunnel från klientroutern till VPS. Jag använder Wireguard som den snabbaste och enklaste att ställa in. Jag har även klientroutrar baserade på Linux (APU2 eller något i OpenWRT). När det gäller vissa Mikrotik / Cisco kan du använda protokollen som finns tillgängliga på dem som OpenVPN och GRE-over-IPSEC.

Identifiering och omdirigering av intressetrafik

Du kan naturligtvis stänga av all internettrafik genom främmande länder. Men sannolikt kommer hastigheten att arbeta med lokalt innehåll att lida mycket av detta. Dessutom kommer bandbreddskraven på VPS att vara mycket högre.

Därför måste vi på något sätt allokera trafik till blockerade webbplatser och selektivt dirigera den till tunneln. Även om en del av den "extra" trafiken kommer dit är det ändå mycket bättre än att köra allt genom tunneln.

För att hantera trafik kommer vi att använda BGP-protokollet och meddela rutter till nödvändiga nätverk från vår VPS till kunder. Låt oss ta BIRD som en av de mest funktionella och bekväma BGP-demonerna.

IP

Med blockering av IP är allt klart: vi meddelar helt enkelt alla blockerade IP:er med VPS. Problemet är att det finns cirka 600 tusen subnät i listan som API:et returnerar, och de allra flesta av dem är /32-värdar. Detta antal rutter kan förvirra svaga klientroutrar.

Därför, när listan bearbetades, beslutades det att sammanfatta upp till nätverket / 24 om det har 2 eller fler värdar. Således reducerades antalet rutter till ~100 tusen. Manuset för detta kommer att följa.

domäner

Det är mer komplicerat och det finns flera sätt. Du kan till exempel installera en genomskinlig Squid på varje klientrouter och göra HTTP-avlyssning där och kika in i TLS-handskakningen för att få den begärda URL:en i det första fallet och domänen från SNI i det andra.

Men på grund av alla möjliga nymodiga TLS1.3 + eSNI, blir HTTPS-analys mindre och mindre verklig för varje dag. Ja, och infrastrukturen på klientsidan blir mer komplicerad - du måste använda åtminstone OpenWRT.

Därför bestämde jag mig för att ta vägen att avlyssna svar på DNS-förfrågningar. Även här börjar all DNS-over-TLS/HTTPS att sväva över ditt huvud, men vi kan (för nu) styra denna del på klienten – antingen inaktivera den eller använd din egen server för DoT/DoH.

Hur avlyssnar man DNS?

Även här kan det finnas flera tillvägagångssätt.

  • Avlyssning av DNS-trafik via PCAP eller NFLOG
    Båda dessa metoder för avlyssning är implementerade i verktyget sidmat. Men det har inte stötts på länge och funktionaliteten är väldigt primitiv, så du behöver fortfarande skriva en sele för den.
  • Analys av DNS-serverloggar
    Tyvärr kan inte de för mig kända rekursorerna logga svar, utan bara förfrågningar. I princip är detta logiskt, eftersom svar, till skillnad från förfrågningar, har en komplex struktur och det är svårt att skriva dem i textform.
  • DNSTap
    Lyckligtvis har många av dem redan stöd för DNSTap för detta ändamål.

Vad är DNSTap?

Förbigå ILV-blockering med DNSTap och BGP

Det är ett klient-serverprotokoll baserat på Protocol Buffers and Frame Streams för överföring från en DNS-server till en samlare av strukturerade DNS-frågor och -svar. I huvudsak överför DNS-servern fråge- och svarsmetadata (typ av meddelande, klient/server-IP, etc.) plus kompletta DNS-meddelanden i den (binära) form som den arbetar med dem över nätverket.

Det är viktigt att förstå att i DNSTap-paradigmet fungerar DNS-servern som en klient och samlaren som en server. Det vill säga att DNS-servern ansluter till insamlaren och inte vice versa.

Idag stöds DNSTap i alla populära DNS-servrar. Men till exempel, BIND i många distributioner (som Ubuntu LTS) byggs ofta av någon anledning utan dess stöd. Så låt oss inte besvära oss med återmontering, utan ta en lättare och snabbare rekursor – Obundet.

Hur fångar man DNSTap?

Det finns vissa nummer CLI-verktyg för att arbeta med en ström av DNSTap-händelser, men de är inte lämpliga för att lösa vårt problem. Därför bestämde jag mig för att uppfinna min egen cykel som gör allt som behövs: dnstap-bgp

Arbetsalgoritm:

  • När den startas laddar den en lista över domäner från en textfil, inverterar dem (habr.com -> com.habr), utesluter brutna linjer, dubbletter och underdomäner (dvs om listan innehåller habr.com och www.habr.com, den kommer att laddas endast den första) och bygger ett prefixträd för snabb sökning genom denna lista
  • Den fungerar som en DNSTap-server och väntar på en anslutning från en DNS-server. I princip stöder den både UNIX- och TCP-sockets, men de DNS-servrar jag känner kan bara använda UNIX-sockets
  • Inkommande DNSTap-paket deserialiseras först till en Protobuf-struktur, och sedan analyseras själva det binära DNS-meddelandet, som finns i ett av Protobuf-fälten, till nivån för DNS RR-poster
  • Det kontrolleras om den begärda värden (eller dess överordnade domän) finns i den laddade listan, om inte, ignoreras svaret
  • Endast A/AAAA/CNAME RR:er väljs från svaret och motsvarande IPv4/IPv6-adresser extraheras från dem
  • IP-adresser cachelagras med konfigurerbar TTL och annonseras till alla konfigurerade BGP-kamrater
  • När du får ett svar som pekar på en redan cachad IP uppdateras dess TTL
  • Efter att TTL löper ut tas posten bort från cachen och från BGP-meddelanden

Ytterligare funktionalitet:

  • Läser om listan över domäner av SIGHUP
  • Håller cachen synkroniserad med andra instanser dnstap-bgp via HTTP/JSON
  • Duplicera cachen på disken (i BoltDB-databasen) för att återställa dess innehåll efter en omstart
  • Stöd för att byta till ett annat nätverksnamnområde (varför detta behövs kommer att beskrivas nedan)
  • IPv6-stöd

restriktioner:

  • IDN-domäner stöds inte ännu
  • Få BGP-inställningar

jag samlade RPM och DEB paket för enkel installation. Bör fungera på alla relativt nya operativsystem med systemd. de har inga beroenden.

Schemat

Så låt oss börja montera alla komponenterna tillsammans. Som ett resultat bör vi få något som denna nätverkstopologi:
Förbigå ILV-blockering med DNSTap och BGP

Arbetets logik, tror jag, framgår tydligt av diagrammet:

  • Klienten har vår server konfigurerad som DNS, och DNS-frågor måste också gå över VPN. Detta är nödvändigt så att leverantören inte kan använda DNS-avlyssning för att blockera.
  • När du öppnar webbplatsen skickar klienten en DNS-fråga som "vad är IP-adresserna för xxx.org"
  • Obundet löser xxx.org (eller tar det från cachen) och skickar ett svar till klienten "xxx.org har en sådan och en sådan IP", duplicerar den parallellt via DNSTap
  • dnstap-bgp tillkännager dessa adresser i FÅGEL via BGP om domänen finns på blockeringslistan
  • FÅGEL annonserar en väg till dessa IP-adresser med next-hop self klientrouter
  • Efterföljande paket från klienten till dessa IP:er går genom tunneln

På servern, för rutter till blockerade webbplatser, använder jag en separat tabell inuti BIRD och den korsar inte OS på något sätt.

Detta schema har en nackdel: det första SYN-paketet från klienten kommer troligen att ha tid att lämna via den inhemska leverantören. rutten meddelas inte omedelbart. Och här är alternativ möjliga beroende på hur leverantören gör blockeringen. Om han bara släpper trafiken är det inga problem. Och om han omdirigerar den till någon DPI så är (teoretiskt) specialeffekter möjliga.

Det är också möjligt att klienter inte respekterar DNS TTL-mirakel, vilket kan få klienten att använda några inaktuella poster från sin ruttna cache istället för att fråga Unbound.

I praktiken orsakade varken den första eller den andra problem för mig, men din körsträcka kan variera.

Serverjustering

För att underlätta rullningen skrev jag roll för Ansible. Den kan konfigurera både servrar och klienter baserade på Linux (designad för deb-baserade distributioner). Alla inställningar är ganska uppenbara och är inställda inventory.yml. Den här rollen är klippt från min stora spelbok, så den kan innehålla fel - dra önskemål välkommen 🙂

Låt oss gå igenom huvudkomponenterna.

bgp

Att köra två BGP-demoner på samma värd har ett grundläggande problem: BIRD vill inte ställa in BGP-peering med den lokala värden (eller något lokalt gränssnitt). Från ordet överhuvudtaget. Att googla och läsa e-postlistor hjälpte inte, de hävdar att detta är av design. Kanske finns det något sätt, men jag hittade det inte.

Du kan prova en annan BGP-demon, men jag gillar BIRD och den används överallt av mig, jag vill inte producera entiteter.

Därför gömde jag dnstap-bgp inuti nätverkets namnutrymme, som är anslutet till roten via veth-gränssnittet: det är som ett rör, vars ändar sticker ut i olika namnutrymmen. I var och en av dessa ändar hänger vi privata p2p IP-adresser som inte går utöver värden, så de kan vara vad som helst. Detta är samma mekanism som används för att komma åt processer inuti älskad av alla Hamnarbetare och andra containrar.

För detta skrevs det manus och den funktionalitet som redan beskrivits ovan för att dra dig själv i håret till ett annat namnområde lades till i dnstap-bgp. På grund av detta måste den köras som root eller utfärdas till CAP_SYS_ADMIN binär via kommandot setcap.

Exempelskript för att skapa namnutrymme

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

fågel.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

Som standard, i Ubuntu, är den obundna binären klämd av AppArmor-profilen, som förbjuder den från att ansluta till alla typer av DNSTap-sockets. Du kan antingen ta bort den här profilen eller inaktivera den:

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

Detta bör förmodligen läggas till i spelboken. Det är naturligtvis idealiskt att korrigera profilen och utfärda de nödvändiga rättigheterna, men jag var för 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

Ladda ner och bearbeta listor

Skript för nedladdning och bearbetning av en lista med IP-adresser
Den laddar ner listan, summerar till prefixet pfx. I dont_add и dont_summarize du kan säga åt IP-adresserna och nätverken att hoppa över eller inte sammanfatta. Jag behövde det. undernätet för min VPS fanns i blockeringslistan 🙂

Det roliga är att RosKomSvoboda API blockerar förfrågningar med standard Python användaragent. Det verkar som att manuskillen fattade det. Därför ändrar vi det till Ognelis.

Än så länge fungerar det bara med IPv4. andelen IPv6 är liten, men det kommer att vara lätt att fixa. Om du inte måste använda bird6 också.

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 att uppdatera
Jag kör den på kronan en gång om dagen, det kanske är värt att dra den var fjärde timme. detta är enligt min mening den förnyelseperiod som RKN kräver av tillhandahållare. Dessutom har de någon annan superbrådskande blockering, som kan komma snabbare.

Gör följande:

  • Kör det första skriptet och uppdaterar listan med rutter (rkn_routes.list) för BIRD
  • Ladda om BIRD
  • Uppdaterar och rensar listan över domäner för dnstap-bgp
  • Ladda om 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

De skrevs utan mycket eftertanke, så om du ser något som kan förbättras - kör på det.

Klientinställningar

Här kommer jag att ge exempel på Linux-routrar, men i fallet Mikrotik/Cisco borde det vara ännu enklare.

Först ställer vi in ​​BIRD:

fågel.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;
}

Således kommer vi att synkronisera de rutter som tas emot från BGP med kärnans routingtabell nummer 222.

Efter det räcker det att be kärnan att titta på den här plattan innan du tittar på standardplattan:

# 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

Allt, det återstår att konfigurera DHCP på routern för att distribuera serverns tunnel-IP-adress som DNS, och schemat är klart.

Begränsningar

Med nuvarande algoritm för att generera och bearbeta listan över domäner innehåller den bl.a. youtube.com och dess CDN.

Och detta leder till det faktum att alla videor kommer att gå via VPN, vilket kan täppa till hela kanalen. Kanske är det värt att sammanställa en lista över populära domänuteslutningar som blockerar RKN för tillfället, magen är tunn. Och hoppa över dem när du analyserar.

Slutsats

Den beskrivna metoden låter dig kringgå nästan alla blockeringar som leverantörer för närvarande implementerar.

I princip, dnstap-bgp kan användas för alla andra ändamål där någon nivå av trafikkontroll behövs baserat på domännamnet. Tänk bara på att i vår tid kan tusen sajter hänga på samma IP-adress (bakom vissa Cloudflare till exempel), så denna metod har en ganska låg noggrannhet.

Men för behoven av att kringgå lås räcker detta.

Tillägg, redigeringar, pull-förfrågningar - välkommen!

Källa: will.com

Lägg en kommentar