Абыход блакіровак РКН з дапамогай DNSTap і BGP

Абыход блакіровак РКН з дапамогай DNSTap і BGP

Тэма даволі з'езджаная, ведаю. Напрыклад, ёсць выдатная артыкул, Але там разглядаецца толькі IP-частка блакіста. Мы ж дадамо яшчэ і дамены.

У сувязі з тым, што суды і РКН блакуюць усё направа і налева, а правайдэры ўзмоцнена спрабуюць не патрапіць пад штрафы, выпісаныя «Рэвізора» - спадарожныя страты ад блакіровак даволі вялікія. Ды і сярод «правамерна» заблакаваных сайтаў шмат карысных (прывітанне, rutracker)

Я жыву па-за юрысдыкцыяй РКН, але на радзіме засталіся бацькі, сваякі і сябры. Так што было вырашана прыдумаць лёгкі для далёкіх ад ІТ асоб спосаб абыходу блакіровак, пажадана зусім без іх удзелу.

У гэтай нататцы я не буду распісваць базавыя сеткавыя рэчы па кроках, а апішу агульныя прынцыпы як мага рэалізаваць гэтую схему. Так што веды як працуе сетка наогул і ў Linux у прыватнасці - must have.

Тыпы блакіровак

Для пачатку асвяжальным у памяці што ж блакуецца.

У выгружаным XML ад РКН некалькі тыпаў блакіровак:

  • IP
  • дамен
  • URL

Мы іх звядзем для прастаты да двух: IP і дамен, а з блакіровак па URL будзем проста выцягваць дамен (дакладней за нас гэта ўжо зрабілі).

Добрыя людзі з Раскамсвабоды рэалізавалі выдатны API, праз які можна атрымліваць тое, што нам трэба:

Доступ да заблакаваных сайтаў

Для гэтага нам патрэбен які-небудзь невялікі замежны VPS, пажадана з безлімітным трафікам – такіх нямала па 3-5 даляраў. Браць трэба ў блізкім замежжы каб пінг быў не надта вялікі, але зноў-такі ўлічваць, што інтэрнэт і геаграфія не заўсёды супадаюць. А паколькі ніякага SLA за пяць даляраў няма – лепш узяць 5 + штукі ў розных правайдэраў для адмоваўстойлівасці.

Далей нам неабходна наладзіць зашыфраваны тунэль ад кліенцкага роўтара да VPS. Я выкарыстоўваю Wireguard як самы хуткі і просты ў наладзе т.я. кліенцкія роўтэры ў мяне таксама на базе Linux (APU2 ці нешта на OpenWRT). У выпадку якіх-небудзь Mikrotik/Cisco можна выкарыстоўваць даступныя на іх пратаколы накшталт OpenVPN і GRE-over-IPSEC.

Ідэнтыфікацыя і перанакіраванне цікавага трафіку

Можна, вядома, загарнуць увогуле ўвесь інтэрнэт-трафік праз замежжа. Але, хутчэй за ўсё, ад гэтага моцна пацерпіць хуткасць працы з лакальным кантэнтам. Плюс патрабаванні да паласы прапускання на VPS будуць моцна вышэйшыя.

Таму нам трэба будзе нейкім чынам выдзяляць трафік да заблакаваных сайтаў і выбарачна яго накіроўваць у тунэль. Нават калі туды патрапіць нейкая частка "лішняга" трафіку, гэта ўсё роўна значна лепш, чым ганяць усё праз тунэль.

Для кіравання трафіку мы будзем выкарыстоўваць пратакол BGP і анансаваць маршруты да неабходных сетак з нашага VPS на кліентаў. У якасці BGP-дэмана возьмем BIRD, як адзін з найболей функцыянальных і зручных.

IP

З блакіроўкамі па IP усё зразумела: проста анансуем усе заблакаваныя IP з VPS. Праблема ў тым, што падсетак у спісе, які аддае API, каля 600 тысяч, і пераважная большасць з іх – гэта хасты /32. Такая колькасць маршрутаў можа збянтэжыць слабыя кліенцкія роўтэры.

Таму было вырашана пры апрацоўцы спісу сумаваць да сеткі /24 калі ў ёй 2 і больш хаста. Такім чынам, колькасць маршрутаў скарацілася да ~100 тысяч. Скрыпт для гэтага будзе надалей.

Дамены

Тут складаней і спосабаў ёсць некалькі. Напрыклад, можна паставіць празрысты Squid на кожным кліенцкім роўтары і рабіць там перахоп HTTP і падглядванне ў TLS-хендшэйк з мэтай атрымання запытанага URL у першым выпадку і дамена са SNI у другім.

Але з-за ўсякіх навамодных TLS1.3+eSNI аналіз HTTPS з кожным днём становіцца ўсё меней рэальным. Ды і інфраструктура з боку кліента ўскладняецца – давядзецца выкарыстоўваць як мінімум OpenWRT.

Таму я вырашыў пайсці па шляху перахопу адказаў на DNS-запыты. Тут таксама над галавой пачынае лунаць усякі DNS-over-TLS/HTTPS, але гэтую частку мы можам (пакуль што) кантраляваць на кліенце – альбо адключыць, альбо выкарыстоўваць свой сервер для DoT/DoH.

Як перахапляць DNS?

Тут таксама могуць быць некалькі падыходаў.

  • Перахоп DNS-трафіку праз PCAP ці NFLOG
    Абодва гэтыя спосабу перахопу рэалізаваны ва ўтыліце sidmat. Але яна даўно не падтрымліваецца і функцыянал вельмі прымітыўны, так што да яе трэба ўсё роўна трэба пісаць абвязку.
  • Аналіз логаў DNS-сервера
    Нажаль, вядомыя мне рэкурсоры не ўмеюць лагагіраваць адказы, а толькі запыты. У прынцыпе гэта лагічна, бо ў адрозненні ад запытаў адказы маюць складаную структуру і пісаць іх у тэкставай форме зацяжка.
  • DNSTap
    На шчасце, многія з іх ужо падтрымлівае DNSTap для гэтых мэт.

Што такое DNSTap?

Абыход блакіровак РКН з дапамогай DNSTap і BGP

Гэта кліент-серверны пратакол, заснаваны на Protocol Buffers і Frame Streams для перадачы з DNS-сервера на нейкі калектар структураваных DNS-запытаў і адказаў. Па сутнасці DNS-сервер перадае метададзеныя запытаў і адказаў (тып паведамлення, IP кліента/сервера і гэтак далей) плюс поўныя DNS-паведамленні ў тым (бінарным) выглядзе ў якім ён працуе з імі па сетцы.

Важна разумець, што ў парадыгме DNSTap DNS-сервер выступае ў ролі кліента, а калектар – у ролі сервера. Гэта значыць DNS-сервер падключаецца да калектара, а не наадварот.

На сённяшні дзень DNSTap падтрымліваецца ва ўсіх папулярных DNS-серверах. Але, напрыклад, BIND у шматлікіх дыстрыбутывах (накшталт Ubuntu LTS) часта сабраны чамусьці без яго падтрымкі. Так што не будзем затлумляцца перазборкай, а возьмем лягчэйшы і хуткі рэкурсор — Unbound.

Чым лавіць DNSTap?

Ёсць некаторае колькасць CLI-утыліт для працы са струменем DNSTap-падзей, але для рашэння нашай задачы яны падыходзяць дрэнна. Таму я вырашыў вынайсці свой ровар, які будзе рабіць усё што неабходна: dnstap-bgp

Алгарытм працы:

  • Пры запуску загружае з тэкставага файла спіс даменаў, інвертуе іх (habr.com -> com.habr), выключае бітыя радкі, дублікаты і паддамены (г.зн. калі ў спісе ёсць habr.com і www.habr.com - будзе загружаны толькі першы) і будуе прэфікснае дрэва для хуткага пошуку па гэтым спісе
  • Выступаючы ў ролі DNSTap-сервера чакае падлучэнні ад DNS-сервера. У прынцыпе ён падтрымлівае як UNIX-так і TCP-сокеты, але вядомыя мне DNS-сервера ўмеюць толькі ў UNIX-сокеты
  • Паступаючыя DNSTap-пакеты дэсерыялізуюцца спачатку ў структуру Protobuf, а затым само бінарнае DNS-паведамленне, змешчанае ў адным з Protobuf-палёў, парыцца да ўзроўня запісаў DNS RR
  • Правяраецца ці ёсць запытаны хост (або яго бацькоўскі дамен) у загружаным спісе, калі не - адказ ігнаруецца
  • З адказу выбіраюцца толькі A/AAAA/CNAME RR і з іх выцягваюцца адпаведныя IPv4/IPv6 адрасы
  • IP-адрасы кэшуюцца з наладжвальным TTL і анансуюцца ва ўсе сканфігураваныя BGP-піры
  • Пры атрыманні адказу, які паказвае на ўжо закэшаваны IP – яго TTL абнаўляецца
  • Пасля заканчэння TTL запіс выдаляецца з кэша і з BGP-анонсаў

Дадатковы функцыянал:

  • Перачытванне спісу даменаў па SIGHUP
  • Сінхранізацыя кэша з іншымі асобнікамі dnstap-bgp праз HTTP/JSON
  • Дубляванне кэша на дыску (у базе BoltDB) для аднаўленне яго змесціва пасля перазапуску
  • Падтрымка пераключэння ў іншы network namespace (навошта гэта трэба будзе апісана ніжэй)
  • Падтрымка IPv6

абмежаванні:

  • IDN дамены пакуль не падтрымліваюцца
  • Мала налад BGP

Я сабраў RPM і DEB пакеты для зручнай усталёўкі. Павінны працаваць на ўсіх адносна свежых OS з systemd, т.я. залежнасцяў у іх ніякіх няма.

схема

Такім чынам, прыступім да зборкі ўсіх кампанентаў разам. У выніку ў нас павінна атрымацца прыкладна такая сеткавая тапалогія:
Абыход блакіровак РКН з дапамогай DNSTap і BGP

Логіка працы, думаю, зразумелая з дыяграмы:

  • У кліента настроены наш сервер у якасці DNS, прычым DNS запыты таксама павінны хадзіць па VPN. Гэта трэба для таго, каб правайдэр не мог выкарыстоўваць перахоп DNS для блакавання.
  • Кліент пры адкрыцці сайта пасылае DNS-запыт выгляду "а якія IP у xxx.org"
  • незвязаны рэзавіт xxx.org (ці бярэ з кэша) і адпраўляе адказ кліенту «у xxx.org вось такія IP», паралельна дублюючы яго праз DNSTap
  • dnstap-bgp анансуе гэтыя адрасы ў BIRD па BGP у тым выпадку, калі дамен ёсць у спісе заблакаваных
  • BIRD анансуе маршрут да гэтых IP з next-hop self кліенцкаму роўтэру
  • Наступныя пакеты ад кліента да гэтых IP ідуць ужо праз тунэль.

На серверы для маршрутаў да заблакаваных сайтаў у мяне ўсярэдзіне BIRD выкарыстоўваецца асобная табліца і з АС яна ніяк не перасякаецца.

У гэтай схеме ёсць недахоп: першы SYN пакет ад кліента, хутчэй за ўсё, паспее сысці праз айчыннага правайдэра т.я. маршрут анансуецца не імгненна. І тут магчымыя варыянты ў залежнасці ад таго як правайдэр робіць блакіроўку. Калі ён проста драпае трафік, то праблем няма. А калі ён рэдырэктыць яго на нейкі DPI, то (тэарэтычна) магчымыя спецэфекты.

Таксама магчымыя цуды з незахаваннем кліентамі DNS TTL, што можа прывесці да таго, што кліент будзе юзать нейкія састарэлыя запісы са свайго пратухлага кэша замест таго каб спытаць Unbound.

На практыку ў мяне ні першае ні другое не выклікала праблем, але ваш мілы вар'ят.

Настройка сервера

Для зручнасці раскочвання я напісаў роля для Ansible. Яна можа наладжваць як сервера, так і кліенты на базе Linux (разлічана на deb-based дыстрыбутывы). Усе налады дастаткова відавочныя і задаюцца ў inventory.yml. Гэтая роля выразана з майго вялікага плэйбука, таму можа змяшчаць памылкі. цягнуць запыты welcome 🙂

Пройдземся па асноўных кампанентах.

Bgp

Пры запуску двух BGP-дэманаў на адным хасце ўзнікае фундаментальная праблема: BIRD ніяк не жадае паднімаць BGP-пірынг з лакалхастам (ці з любым лакальным інтэрфейсам). Ад слова зусім. Гулянне і чытанне mailing-lists не дапамагло, тамака сцвярджаюць што гэта by design. Магчыма, ёсць нейкі спосаб, але я яго не знайшоў.

Можна паспрабаваць іншы BGP-дэман, але мне падабаецца BIRD і ён выкарыстоўваецца ўсюды ў мяне, не хочацца пладзіць сутнасці.

Таму я схаваў dnstap-bgp ўнутр network namespace, якое звязана з каранёвым праз veth інтэрфейс: гэта як труба, канцы якой тырчаць у розных namespace. На кожны з гэтых канцоў мы вешаем прыватныя p2p IP-адрасы, якія за межы хаста не выходзяць, таму могуць быць любымі. Гэта той жа механізм які выкарыстоўваецца для доступу да працэсаў усярэдзіне. каханага ўсімі Docker і іншых кантэйнераў.

Для гэтага быў напісаны скрыпт і ў dnstap-bgp быў дададзены ўжо апісаны вышэй функцыянал перацягвання сябе за валасы ў іншы namespace. З-за гэтага яго неабходна запускаць пад root або выдаць бінарніку CAP_SYS_ADMIN праз каманду setcap.

Прыклад скрыпту для стварэння 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

Па змаўчанні ў Ubuntu бінарнік Unbound заціснуты AppArmor-профілем, які забараняе яму канэкціцца да ўсякіх тамака DNSTap-сокетам. Можна або выдаліць нафіг гэты профіль, ці адключыць яго:

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

Гэта, мусіць, трэба дадаць у плэйбук. Ідэальна, вядома, паправіць профіль і выдаць патрэбныя правы, але мне было лянота.

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

Спампоўка і апрацоўка спісаў

Скрыпт для спампоўкі і апрацоўкі спісу IP-адрасоў
Ён спампоўвае спіс, сумарызуе да прэфікса pfx. У dont_add и dont_summarize можна сказаць IP і сеткі, якія трэба прапусціць ці не сумарызаваць. Мне гэта было патрэбна т.я. падсетка майго VPS апынулася ў блакалісты 🙂

Самае смешнае што API РосКомСвободы блакуе запыты з дэфолтным юзэр-агентам Пітона. Відаць скрыпт-кідзі дасталі. Таму мяняем яго на Агнеліса.

Пакуль што ён працуе толькі з IPv4 т.я. доля IPv6 невялікая, але гэта будзе лёгка выправіць. Хіба што давядзецца выкарыстоўваць яшчэ і 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)

Скрыпт для абнаўлення
Ён у мяне запускаецца па кроне раз у суткі, можа варта тузаць раз у 4 гадзіны т.я. гэта, на маю думку, перыяд абнаўлення які РКН патрабуе ад правайдэраў. Плюс, ёсць нейкія яшчэ супертэрміновыя блакіроўкі ў іх, якія можа і хутчэй прылятаюць.

Робіць наступнае:

  • Запускае першы скрыпт і абнаўляе спіс маршрутаў (rkn_routes.list) для BIRD
  • Рэлаадыт BIRD
  • Абнаўляе і падчышчае спіс даменаў для dnstap-bgp
  • Рэлаадыт 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

Яны былі напісаны не асабліва задумваючыся, таму калі бачыце што можна палепшыць - адважвайцеся.

Настройка кліента

Тут я прывяду прыклады для Linux-роўтэраў, але ў выпадку Mikrotik/Cisco гэта павінна быць яшчэ прасцей.

Для пачатку наладжваем 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;
}

Такім чынам, мы будзем сінхранізаваць маршруты, атрыманыя з BGP, з табліцай маршрутызацыі ядра за нумарам 222.

Пасля гэтага дастаткова папрасіць ядро ​​глядзець у гэтую таблічку перад тым як зазіраць у дэфолтную:

# 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

Усё, засталося наладзіць DHCP на роўтары на раздачу тунэльнага IP-адрасы сервера ў якасці DNS і схема гатова.

Недахопы

Пры бягучым алгарытме фармавання і апрацоўкі спісу даменаў у яго пападае, у тым ліку, youtube.com і яго CDNы.

А гэта прыводзіць да таго, што ўсе відэа будуць ехаць праз VPN, што можа забіць увесь канал. Магчыма варта скласці нейкі спіс папулярных даменаў-выключэнняў, якія блакаваць у РКН пакуль што кішка тонкая. І прапускаць іх пры парсінгу.

Заключэнне

Апісаны спосаб дазваляе абыходзіць практычна любыя блакіроўкі, якія рэалізуюць правайдэры на дадзены момант.

У прынцыпе, dnstap-bgp можна выкарыстоўваць для любых іншых мэт дзе неабходзен нейкі ўзровень кіравання трафіку на аснове даменнага імя. Толькі трэба ўлічваць, што ў наш час на адным і тым жа IP-адрасе можа вісець тысяча сайтаў (за якім-небудзь Cloudflare, напрыклад), так што гэты спосаб мае даволі нізкую дакладнасць.

Але для патрэб абыходу блакіровак гэтага цалкам дастаткова.

Дапаўненні, праўкі, пуллреквесты - вітаюцца!

Крыніца: habr.com

Дадаць каментар