Описание работы программы для автоматического обхода блокировок в интернете, код программы лежит на репозитории Antiblock.
Приблизительно в мае 2022 года был заблокирован один из доменов YouTube (yt3.ggpht.com), через который происходит выгрузка превью и логотипов каналов. Если до этого блокировки меня особо не утруждали, то YouTube без картинок, для активного пользователя, это тяжело. Далее были предприняты несколько разных подходов к оптимальному обходу блокировки этого домена.
Попытка номер один - Свой собственный VPN
Весной 2022 года стало актуально создавать собственные VPN на основе мощностей арендных VPS. Я тоже создал свой VPN WireGuard. Почитать как это сделать самому можно например в статье.
Пропускание всего трафика с телефона или планшета через VPN приводило к быстрой разрядке батареи. И на некоторые сайты не заходило через VPN, например Госуслуги или Avito. Поэтому было решено придумать более гибкий и адаптивный метод, а именно маршрутизацию сразу на роутере. Но для маршрутизации на роутере необходимо иметь роутер с поддержкой WireGuard, а значит необходим роутер, на который можно установить OpenWrt. Мой предыдущий роутер (RT-AC57U V3) не имел такой возможности, а так как я хотел достаточно мощнее устройство для экспериментов и настройке сетевого диска, то выбор пал на Beelink U59 Pro. К нему была куплена пара мощных антенн и другой Wi-Fi модуль (Mediatek MT7921K). Подробнее о сборке и настройке я расскажу в следующих статьях. Таким образом у меня появился x86 роутер с возможностью компилировать и запускать на нем код. И была предпринята следующая попытка.
Попытка номер два - Таблица маршрутизации
Второй попыткой настроить обход блокировок было внести в таблицу маршрутизации все заблокированные IP-адреса, выложенные на сайте antifilter.download. В списке allyouneed.lst находится около десяти тысяч IP-адресов и подсетей, что достаточно легко помещается в таблицу маршрутизации роутера. Для автоматического внесения адресов был написал Bash скрипт.
ip r | grep VPN | cut -d ' ' -f1 | xargs -I{} ip route del {}
curl https://antifilter.download/list/allyouneed.lst > ip.txt
cat ip.txt | xargs -I{} ip route add {} dev VPN
Если добавить этот скрипт в автозапуск Cron, то таблица маршрутизации будет иметь актуальный список IP-адресов и подсетей.
Скрипт работал, но быстро обнаружились его изъяны. IP-адреса доменов входящие в CDN очень часто меняются, поэтому их нет в списке allyouneed.lst. А домен, ради которого всё затевалось (yt3.ggpht.com), как раз тоже входит в CDN Google. Поэтому от этого подхода пришлось отказаться, но он может кому-то пригодится из-за простой настройки и отсутствию необходимости компилировать код под своё устройство.
Попытка номер три - BPF DPI
"Чтобы бороться с DPI надо думать, как DPI".
Почитав ранее статьи про BPF, я подумал реализовать простой DPI на основе BPF, которая будет маршрутизировать трафик в зависимости от SNI TLS Handshake. Это возможно было сделать как напрямую маршрутизируя пакеты через BPF, так и выставляя флаг nf_conntrack, а далее маршрутизировать через nftables. Но тут снова сыграл свою роль самый важный для меня домен (yt3.ggpht.com), общение с ним идет не через TLS 1.3, а через QUIC, а значит и nf_conntrack не будет толком работать, да и пакеты зашифрованные, хоть и известным ключом, и для анализа придется их детектировать и расшифровывать. Для обычно не продуктивных роутеров это будет очень тяжелая нагрузка. От этого подхода тоже было решено отказаться.
Попытка номер четыре - Автоматическая антиблокировка
Осознав плюсы и минусы предыдущих попыток, было решено маршрутизировать в зависимости от DNS пакетов. Была написана программа прокси DNS запросов, которая автоматически добавляет IP-адреса заблокированных доменов в таблицу маршрутизации. И удаляет при истечении времени жизни IP-адреса. Программа выложена на репозитории.
Кратко опишу работу программы:
Для быстрой проверки входит ли домен в список заблокированных доменов, необходимо добавить заблокированные домены в хеш-таблицу. Перепробовав разные хеш-таблицы для Си, ни одна из них не имела нужные характеристики. Была написана библиотека для хеш-таблицы, подробнее о ней будет рассказано в следующей статье. Для экономии памяти заблокированные домены хранятся не как массив указателей на нуль-терминированные строки, а как длинный массив лежащих подряд нуль-терминированных строк. Экономия памяти, потому что malloc на каждую строку занимал бы служебную информацию. А в хеш-таблице можно хранить смещение начала строки от начала массива, тем самым хеш-таблица состоит из четырехбайтных int, а не восьмибайтных pointer. Список заблокированных доменов автоматически обновляется каждые 12 часов в отдельном потоке. Домены скачиваются с сайта. Код описан в файле urls_read.c.
При поступлении DNS запроса от клиента, id запроса заменяется на внутренний, чтобы не было совпадения id номеров с разных клиентов. Старый id, IP-адрес, порт, время прихода пакета и хэш домена запоминаются в массив в поле с номером нового id. При увеличении нагрузки до предельной, если ответы на запросы не успевают приходить, то мы сможем начать отбрасывать пакеты от клиента на этапе поиска нового внутреннего id.
При поступлении ответа от DNS сервера, по пришедшему id смотрим поле с этим номером в массиве из предыдущего абзаца. Проверяем не истекло ли время возврата пакета, проверяем совпадение хэш домена с сохранённым, если всё хорошо, то помещаем пришедший пакет в кольцевой буфер для обработки другим потоком. Кольцевой буфер необходим для постоянства в использовании памяти. Код обработки запросов описан в файле net_data.c.
Бывает много разных типов DNS ответов, но нас интересуют два варианта типа “A” и “CNAME”. Тип “A” делает прямое соответствие между доменом и IP-адресом. Тип “CNAME” делает соответствие между данным доменом и новым доменом. Если встречаем заблокированный домен с ответом типа “A”, то добавляем IP-адрес в таблицу маршрутизации, и добавляем IP-адрес в хэш таблицу с ключом IP-адрес, а значением временем истечения жизни IP-адреса. Если встречаем заблокированный домен с ответом типа “CNAME”, то добавляем домен в список временно заблокированных, а так же добавляем домен с хэш таблицу с ключом домен, а значением временем истечения жизни домена. Код описан в файле dns_ans.c.
Отдельный поток раз в минуту проверяет истекшие IP-адреса и домены и удаляет их. Код описан в файле ttl_check.c.
Тестирование
Тестирование проводилось на отдельно написанной программе, которая эмулирует большое количество DNS запросов. Тестировалось на миллионе самых популярных доменов по версии CloudFlare. Моя программа выдерживала 100 000 запросов в минуту. А так же тестирование проводилось с использованием AddressSanitizer и MemorySanitizer, никаких проблем выявлено не было. При работе программа потребляет очень мало оперативной памяти, примерно 12 MB, причем 8 MB это объем заблокированных доменов, тем самым подходит даже самым простым роутерам.
Разбор на конкретном примере
Например я хочу зайти на RuTracker.
Программа детектировала DNS запрос с заблокированным доменом "RuTracker" и добавила два IP адреса "172.67.187.38" и "104.21.72.173" в таблицу маршрутизации и запомнила что в 15:44:53 необходимо удалить их, так как их актуальность истечет.
Моя программа пишет лог действий в формате CSV:
15:40:30,add ip,rutracker.org,172.67.187.38,15:44:53
15:40:30,add ip,rutracker.org,104.21.72.173,15:44:53
Таблица маршрутизации:
root@OpenWrt:~# ip r | grep VPN
104.21.72.173 via 192.168.6.37 dev VPN
172.67.187.38 via 192.168.6.37 dev VPN
Мы видим внесенные IP адреса, но уже 15:45:19 IP адреса удаляются.
Лог программы:
15:45:19,del ip,,172.67.187.38,15:44:53
15:45:19,del ip,,104.21.72.173,15:44:53
Новая таблица маршрутизации:
root@OpenWrt:~# ip r | grep VPN
Таким образом мы динамически узнаем нынешний IP адрес домена и добавляем именно актуальный IP адрес, тем самым имеем возможность использовать домены лежащие на CDN.
Комментарии (55)
Actaeon
29.06.2023 19:08Хотел написать, что в принципе, на функциональном уровне, это решается много проще , посредством того же privoxy, причем, прописывать там надо скорее прокси в рунет, оставляя заграничный по умолчанию, но если вам так сильно нужен udp ...
karen07 Автор
29.06.2023 19:08+5Одно из преимуществ моей программы это очень маленькое потребление памяти, примерно 12 мегабайт, из них 8 мегабайт занимают все заблокированные домены. Я постараюсь дополнить пост примерами реального лога и потреблением памяти.
microArt
29.06.2023 19:08Пожалуйста, распишите подробно, как можно настроить Privoxy?
Actaeon
29.06.2023 19:08Минимальная настройка - открываете config (config.txt в win) дописываете
forward / outerworld_proxy:port
forward .ru interrusia_proxy:port
forward .su interrusia_proxy:port
(если соотв прокси не нужен, а нужно - прямое соединение ставите просто точку) . После чего на 127.0.0.1:8118 privoxy поднимает вам прокси перенаправляющий вас туда куда вам нужно.
Где брать внешний прокси ?? Ну тут каждый исхитряется как может. Бомж-вариант - берете смартфон , ставите на него N штук vpn и Android Proxy Server. Главное преимущество - никаких openwrt, никаких BPF - ничерта не нужно, матушка в свои 71 настраивает в режиме телефонной консультации ...
AlexGluck
29.06.2023 19:08+3Возможно против блокировок поможет "метод Пригожина", но я ни к чему не призываю. Хотя если уважаемый Максут Шадаев захочет о чем нибудь поговорить с Липовыми, может попросить проработать откровенные недостатки базы заблокированных сервисов. Особенно внесудебные блокировки. Стабильно рабочая сеть важна в стране. Как они ещё госуслуги умудрились не заблокировать я удивлен.
Alexsey
29.06.2023 19:08+1Помнится когда я начал заворачивать трафик до yt3.ggpht.com через VPN у меня начались какие-то нехорошие приколы, но вот в упор не могу вспомнить какие... Но достаточно серьезные чтобы сидеть без аватарок и картинок в community постах (превью как не удивительно почему-то грузятся нормально)
S-trace
29.06.2023 19:08Кстати да, у меня превьюшки тоже грузились и без yt3.ggpht.com, но спасибо автору статьи что ткнул носом - добавил в zapret-hosts-user.txt, обновил списки и теперь аватарки отображаются снова
Zipdots
29.06.2023 19:08У меня из таких приколов - долгий "запуск" видео, который начинается спустя N дней нормальной работы завернутого трафика в VPN. Видео просто начинает 5-15 секунд крутить колесом загрузки. Пока не решил.
TheDarkKRONOS
29.06.2023 19:08+9Вообще на Android в WireGuard клиенте можно и не весь трафик пропускать, а только определённые приложения. Там надо зайти в добавленный конфиг, нажать на редактирование и будет пункт с исключёнными приложениями. Так можно исключать банк. приложения, поставить 2 браузера, один из которых идёт по впн, а второй по обычному каналу.
bit314
29.06.2023 19:08О прикольно, не замечал такой фишки. Удобно однако.
vikarti
29.06.2023 19:08+1Она у большинства "нормальных" VPN-клиентов под андроид есть.
К сожалению у некоторых (насколько помню — Mullvad когда он еще не был сам заблокирован и Proton VPN) — добавление в список исключений — очень долго если приложений много.
Ну и VPN на андроид = прощай блокировка рекламы (ну или надо ее делать на сервере, если этого достаточно) если она тоже через VPN а сочетать и то и другое умеет штатно только парочка Adguard VPN + Adguard, но вот только они платные.TheDarkKRONOS
29.06.2023 19:08Kiwi Browser + uBlock origin + Tampermonkey со скриптами под Wireguard и вот резалка рекламы работает
vikarti
29.06.2023 19:08Да? И во всех приложениях который используют WebView или Custom Tab будет работать?
С браузером то проблема решается просто (Firefox и расширения). С Custom Tab там — теоретически проблема решается тем же Firefox'ом а на практике — есть приложения где тупит все из-за этого.
TheDarkKRONOS
29.06.2023 19:08Если нужно в приложениях под впн, то срабатывает Wireguard + Adguard DNS (на андроиде его как кастомный прописать надо). Реклама в играх режется под vpn на удивление. Тут зависит от того, какой список программ на андроид в клиенте прописать. Там есть список исключений (то есть программы, которые не надо через впн проводить), тогда система также через впн ходит (и WebView и прочие, если не исключать) или же есть белый список, то есть только определённые программы ходят через впн, тогда система ходит напрямую.
vikarti
29.06.2023 19:08+1Меня вариант с Adguard + Adguard VPN вполне устраивает. Удобно. Весьма.
При этом Wireguard через свою VPS(где сервер matrix живет) тоже настроен, но в запас.
Dolios
29.06.2023 19:08+5для маршрутизации на роутере необходимо иметь роутер с поддержкой WireGuard, а значит необходим роутер, на который можно установить OpenWrt.
Кинетики на родной прошивке умеют в WireGuard.
PlatinumThinker
29.06.2023 19:08+1и микротики
vikarti
29.06.2023 19:08В RouterOS 7
Для которой, судя по сайту, до сих пор нет long-term сборки (я в курсе что из Winbox'а такой видно 7.5 но на сайте то нет)
yupych
29.06.2023 19:08Умеют, но у них нет аппаратного ускорения шифрования и сколько-нибудь нормальной скорости работы не добиться. На Микротике только на старших моделях можно что-то выжать. Кинетик быстро упирается в производительность процессора.
dartraiden
29.06.2023 19:08WireGuard и не требует аппаратного ускорения шифрования. Какие вообще процессоры умеют аппаратно ускорять ChaCha20 и Poly1305?
Если же вы говорите про аппаратное ускорение AES, то, во-первых, AES в WireGuard не используется, во-вторых кинетики прекрасно ускоряют его, кроме совсем уж старых моделей.
Вышенаписанное относится к роутерам с MIPS-процами. В новые кинетики сейчас вообще ставят ARM-процессоры, в их группе в телеграме скидывали результаты бенчмарков - спокойно жуют сотни мегабит.
DrrRos
29.06.2023 19:08+1А теперь задание со звёздочкой - обход блокировок когда блокировка не на стороне РКН, а на стороне сервера.
karen07 Автор
29.06.2023 19:08Тоже думал на эту тему, но тут уже без DPI ни как не обойтись)Только если кто то составит отдельный список таких)
vikarti
29.06.2023 19:08Любое решение с VPN но точка выхода НЕ в России?
mayorovp
29.06.2023 19:08Это сломает Госуслуги.
SuhoffGV
29.06.2023 19:08И не только. Не раз натыкался на рос сайты которые тупо не открываются через vpn
vikarti
29.06.2023 19:08Бывает смешнее — сайт открывается но логин не работает. Потому что site.ru и api.site.ru — за cloudflare но вот у api.site.ru настройки более более жесткие.
Если блокировки на той стороне — можно просто считать их блокировками на этой и просто сайт в туннель заворачивать. Если же это становится неудобно — заворачивать в туннель все по умолчанию (с Youtube/Facebook сложно но списки сетей Google вполне себе публичны например) а то что должно быть в России — принудительно НЕ заворачивать. Либо руками по доменам либо раз список сгенерить либо вообще с https://antifilter.network/bgp брать — есть там списочек ру-сетей.
У меня на основном так сказать месте откуда доступ — схема с получением по BGP списков куда нельзя ходить и отправление трафика на эти сети куда следует. В дополнение — ручное задание по DNS-имени (у роутера и машин за ним — один и тот же DNS-резолвер так что если уж резолвинг на роутере решил что habr.com это 1.2.3.4 то машинки за ним тоже полезут за habr.com через 1.2.3.4).
С копией антизапрета (запуск их контейнера у себя) возится пока не хочется. В том числе потому что ну очень не хочется танцы с прокси устраивать, придется ж не только для браузеров это городить, например для gradle еще надо.
(и без того хватает проблем — приходится вот в Firefox + MultiAccount Containers + Container Proxy несколько групп сайтов через отдельные спецпрокси и в данном случае тема совсем не про цензуру).
HiroX
29.06.2023 19:08Давно есть идея как реализовать подобное: самописная прокся, которая будет автоматически повторять запрос через какой-нить парент при обнаружении 403/access denied. На минутку призадумался, а может это вообще реализуемо через прозрачный squid
karen07 Автор
29.06.2023 19:08нет, потому что мало смотреть на уровне http/https, надо смотреть на уровне tcp/udp.
olartamonov
29.06.2023 19:08+2Там вообще может быть не 403, а 200 ОК — и страница-заглушка «извините, мы с вами больше не работаем».
Вот, например, порнхаб...
Но пока мы делаем это чисто для себя, вполне достаточно вести ручками список таких URL'ов. У меня, например, стоит ящик с pfSense, к нему из разных локаций (дача, другие квартиры — клиенты на роутерах, мобильные клиенты на мобилах) коннектятся по WireGuard (заодно получая доступ к локальным ресурсам этого ящика, от умного дома до файлопомойки и торрентокачалки), а на ящике уже стоит VPN и policy-based routing со списками, формируемыми как вручную, так и из подгружаемых снаружи перечней IP или доменов.
Pavel7
29.06.2023 19:08Если сервер блокирует по странам, то подойдёт geo ip роутинг. Есть отличная статья с готовым скриптом по выгрузке подсетей разных стран в формате удобном для запихивания в route add: https://habr.com/en/articles/659655/
Поднимаем на роутере VPN соединения к серверам в нужных странах и роутим подсети этих стран через эти VPN.
blind_oracle
29.06.2023 19:08+3Я задолбался бороться с отдельными блокировками и просто зароутил все зарубежные (с т.з. RIPE) сети в VPN, а российские идут напрямую. Это не идеально, конечно, но лучше чем другие подходы как по мне. Если есть какие-то нестыковки - просто добавляю в исключения.
karen07 Автор
29.06.2023 19:08на моем роутере antiblock стоит уже 3 месяца и не возникает проблем, список доменов обновляется оперативно, так что если есть возможность, можете попробовать)
blind_oracle
29.06.2023 19:08Зачем? Зашифрованные DNS оно не отловит, да и стояла у меня подобная система какое-то время: https://habr.com/ru/articles/467547/
Javian
29.06.2023 19:08+1Есть статья с оригинальным методом получения списка заблокированных сайтов:
Точечный обход блокировок PKH на роутере с OpenWrt с помощью WireGuard и DNSCrypt (часть 1)
https://habr.com/ru/articles/440030/subnet.lst — список заблокированных подсетей, изменяется не часто.
ip.lst — список заблокированных адресов, прям из списка РКН
community.lst — список заблокированных адресов, который составляется комьюнити antifilter.download
ValdikSS
Ваш подход похож на опцию --ipset в DNS-резолвере dnsmasq, которая добавляет IP-адреса резолва в ip set, а не в таблицу маршрутизации. IP set'ы хорошо оптимизированы, адреса из set'а могут удаляться по таймауту автоматически, ядром, но требуют маркировки пакетов средствами netfilter, чтобы направить их в нужную таблицу маршрутизации.
Несколько недоработок вашей реализации:
Отсутствует поддержка AAAA-записей. На провайдерах с IPv6 (в России это порядка 7% интернет-трафика) обход не заработает — нужно хотя бы удалять AAAA-ответ из резолва, если нет желания обрабатывать полноценно.
Отсутствует поддержка записи SVCB/HTTPS (type 65), которая используется на устройствах Apple. Она имеет приоритет выше, чем A/AAAA, из-за чего iPhone может отправлять запросы в обход ваших правил.
На некоторых устройствах и некоторых браузерах по умолчанию включены DNS-over-HTTPS/TLS, из-за чего ваш резолвер не будет получать запросы. У Apple шифрование запросов отключается блокировкой доменов 'mask.icloud.com', 'mask-h2.icloud.com', 'mask.apple-dns.net', а в Firefox — 'use-application-dns.net'.
vagonovozhaty
Я крайне не рекомендую блокировать какие-либо домены Apple. Это может привести к непредсказуемым последствиям. Какие-то функции перестанут работать или будут работать медленно, через раз, и вы замучаетесь искать причину.
Лучше в dnsmasq что-то типа:
address=/use-application-dns.net/
address=/_dns.resolver.arpa/
если такой вариант для вас актуален.
sergeymakinen
Однако, блокировка
mask.icloud.com
иmask-h2.icloud.com
отдельно документирована для отключения iCloud Private Relay, что может быть полезно для исключения DNS-запросов мимо своего резолвера.karen07 Автор
Как раз на OpenWRT в пакете https-dns-proxy по умолчанию блочатся эти домены)
werter_l
На ваш, кхм, роутер я бы накатил сперва proxmox ve + pfsense в виде вирт. машины.
Еще и запас мощности остается для экспериментов.
Вот это был бы РОУТЕР :)
P.s. Если заинтересуетесь, цикл статей по proxmox, pfsense etc https://forum.netgate.com/topic/163435/proxmox-ceph-zfs-pfsense-и-все-все-все-часть-2/
karen07 Автор
Я один раз предпринимал попытку поставить Arch и всё настроить для роутера, но оказалось очень не легко и время ограничено было)
werter_l
Пробуйте
karen07 Автор
Спасибо за развернутую рецензию, со временем я добавлю поддержку остальных режимов)