Тэма даволі з'езджаная, ведаю. Напрыклад, ёсць выдатная артыкул, Але там разглядаецца толькі 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?
Гэта кліент-серверны пратакол, заснаваны на 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, т.я. залежнасцяў у іх ніякіх няма.
схема
Такім чынам, прыступім да зборкі ўсіх кампанентаў разам. У выніку ў нас павінна атрымацца прыкладна такая сеткавая тапалогія:
Логіка працы, думаю, зразумелая з дыяграмы:
У кліента настроены наш сервер у якасці 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
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-сокетам. Можна або выдаліць нафіг гэты профіль, ці адключыць яго:
Гэта, мусіць, трэба дадаць у плэйбук. Ідэальна, вядома, паправіць профіль і выдаць патрэбныя правы, але мне было лянота.
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, напрыклад), так што гэты спосаб мае даволі нізкую дакладнасць.
Але для патрэб абыходу блакіровак гэтага цалкам дастаткова.