Автоматический обход блокировок

в 18:36, , рубрики: C, linux, OpenWrt, vpn, высокая производительность, обход блокировок, Разработка под Linux, Сетевые технологии

Описание работы программы для автоматического обхода блокировок в интернете, код программы лежит на репозитории 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 роутер с возможностью компилировать и запускать на нем код. И была предпринята следующая попытка.

Beelink U59 Pro с антеннами

Beelink U59 Pro с антеннами

Попытка номер два - Таблица маршрутизации

Второй попыткой настроить обход блокировок было внести в таблицу маршрутизации все заблокированные 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.

Автор: Хачатрян Карен

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js