Единая точка выхода в web, I2P, TOR и обход блокировок

Коварный план

Преамбула… Даная статья была написана ещё летом но, по независящим от автора причинам, немножко подзадержалась…

Однажды, жарким летним вечером, после очередной введённой в консоли браузера команды вида :set content.proxy socks://localhost:9050, автор сего опуса понял, что дальше так жить нельзя и пора приводить выход во всякие скрытосети, а заодно и обход блокировок имени известной организации к какому-то единому, для любого софта вообще и браузера в частности, «общему знаменателю». А как приводить? Разумеется так, что-бы прокси сервер сам «понимал», через какой вышестоящий прокси отправлять и принимать трафик в зависимости от введённого адреса. Вторая цель, вытекающая из первой, вышестоящие прокси могут работать либо как http, либо как socks и оба протокола должны поддерживаться входным прокси. Ну и сам софт должен быть более менее актуальным, что-б в случае ошибок или «хотения фич», не приходилось грустно смотреть на одинокую репу на гитхабе, а то и вообще на каком-нибудь сорсфорже.
Итак цели поставлены!

На самом деле особых мук не было. Ибо, по большому счёту, из имеющихся известных прокси серверов поставленным требованиям удовлетворяли два. Это privoxy и tinyproxy. Но tinyproxy оказался более живым, более легковесным и более простым, поэтому и был выбран и немедленно установлен (в качестве примера используется текущая версия Manjaro Linux).

sudo pacman -Syu tinyproxy

Само собой, до этого, уже были установлены настроены пакеты tor и i2pd. (sudo pacman -Syu tor i2pd).

Итак, настроим базовые перенаправления, что-бы в обычный web ходило напрямую, а в *.i2p и *.onion через соответствующие вышестоящие (parent) proxy.

/etc/tinyproxy/tinyproxy.conf:

User tinyproxy
Group tinyproxy
PidFile "/var/run/tinyproxy/tinyproxy.pid"

Port 8888
Listen 127.0.0.1
Timeout 600

DefaultErrorFile "/usr/share/tinyproxy/default.html"
StatFile "/usr/share/tinyproxy/stats.html"

Syslog On
# Set the logging level. Allowed settings are:
#   Critical, Error, Warning, Notice, Connect, Info
LogLevel Info

MaxClients 100
MinSpareServers 5
MaxSpareServers 20
StartServers 10
MaxRequestsPerChild 0

Allow 127.0.0.1

ViaProxyName "tinyproxy"

## Parent proxy for TOR hosts
upstream socks5 127.0.0.1:9050 ".onion"
## Parent proxy for I2P hosts
upstream socks5 127.0.0.1:4447 ".i2p"

##### End of static configuration #####

Для начала практически все параметры в конфиге остаются по умолчанию.


  • Сохраняем
  • Запускаем: sudo systemctl enable --now tinyproxy
  • Проверяем: journalctl -f -u tinyproxy, параллельно пытаемся зайти на i2p и onion ресурсы (настроив браузер на использование http proxy http://localhost:8888) и видим в лог файле сообщения о перенаправлениях на parent proxy’s:
    июл 20 01:36:16 dell-lnx tinyproxy[198356]: Request (file descriptor 6): GET http://flibusta.i2p/ HTTP/1.1
    июл 20 01:36:17 dell-lnx tinyproxy[198356]: Found upstream proxy socks5 127.0.0.1:4447 for flibusta.i2p
    июл 20 01:36:17 dell-lnx tinyproxy[198356]: opensock: opening connection to 127.0.0.1:4447
    июл 20 01:36:17 dell-lnx tinyproxy[198356]: opensock: getaddrinfo returned for 127.0.0.1:4447
    июл 20 01:36:17 dell-lnx tinyproxy[198356]: Established connection to socks5 proxy "127.0.0.1" using file descriptor 7.
    июл 20 01:36:40 dell-lnx tinyproxy[198356]: Closed connection between local client (fd:6) and remote client (fd:7)
    ...
    июл 20 01:39:36 dell-lnx tinyproxy[214495]: Found upstream proxy socks5 127.0.0.1:9050 for ilitafrzzgxymv6umx2ux7kbz3imyeko6cnqkvy4nisjjj4qpqkrptid.onion
    июл 20 01:39:36 dell-lnx tinyproxy[214495]: opensock: opening connection to 127.0.0.1:9050
    июл 20 01:39:36 dell-lnx tinyproxy[214495]: opensock: getaddrinfo returned for 127.0.0.1:9050
    июл 20 01:39:36 dell-lnx tinyproxy[214495]: Established connection to socks5 proxy "127.0.0.1" using file descriptor 7.

Ну что-ж, связка прокси базово работает, теперь начинается самое интересное — обход блокировок роскомнадзора. К сожалению, tinyproxy не поддерживает внешние файлы со списком parent proxy, но это не должно быть препятствием. Ведь мы можем сгенерировать монолитный конфиг «on the fly».


  1. Копируем имеющийся конфиг tinyproxy под новым именем:

    cp /etc/tinyproxy/tinyproxy.conf /etc/tinyproxy/tinyproxy.conf.static

  2. Слегка правим новый /etc/tinyproxy/tinyproxy.conf.static: LogLevel InfoLogLevel Error


  3. Создаём юнит который будет клонировать репозиторий проекта zapret.info — sudo systemctl edit --full --force z-i-prepare.service:

    # /etc/systemd/system/z-i-prepare.service
    [Unit]
    Description=Zapret Info repository cloner
    ConditionPathExists=|!/usr/local/lib/z-i/
    ConditionFileNotEmpty=|!/usr/local/lib/z-i/dump.csv
    Wants=local-fs.target
    After=local-fs.target
    Wants=network.target
    After=network.target
    #
    [Service]
    Type=oneshot
    User=tinyproxy
    Group=tinyproxy
    ExecStartPre=+/usr/bin/mkdir -p /usr/local/lib/z-i
    ExecStartPre=+/usr/bin/chown tinyproxy:tinyproxy /usr/local/lib/z-i
    ExecStartPre=+/usr/bin/chmod 0750 /usr/local/lib/z-i
    ExecStart=git clone --depth 1 https://github.com/zapret-info/z-i.git /usr/local/lib/z-i

  4. Создаём юнит который будет генерировать конфиг tinyproxy, в рантайме — sudo systemctl edit --full --force tinyproxy-cfg-generator.service:

    # /etc/systemd/system/tinyproxy-cfg-generator.service
    [Unit]
    After=z-i-prepare.service
    Wants=z-i-prepare.service
    #
    [Service]
    Type=oneshot
    User=tinyproxy
    Group=tinyproxy
    Environment="PATH=/usr/local/bin:/usr/sbin:/usr/bin"
    ExecStart=tinyproxy-cfg-gen.sh
    StandardOutput=file:/run/tinyproxy/tinyproxy.conf

    … и собственно скрипт /usr/local/bin/tinyproxy-cfg-gen.sh к нему:

    #!/usr/bin/env sh
    # tinyproxy-cfg-gen.sh -- tinyproxy dynamic config generator to stdout.
    awk -F';' '{print "upstream socks5 127.0.0.1:9050 \"" $2"\""}' /usr/local/lib/z-i/dump.csv|tr -d '*'|sort|uniq|grep -v '\s\"\"'>/tmp/tinyproxy.conf.dynamic
    cat /etc/tinyproxy/tinyproxy.conf.static /tmp/tinyproxy.conf.dynamic

  5. Таймер и сервис, который раз в сутки будет выкачивать обновления списка и рестартовать основной сервис: sudo systemctl edit --full --force z-i-update-daily.timer:

    # /etc/systemd/system/z-i-update-daily.timer
    [Unit]
    Description=Zapret Info daily update
    After=network.target
    Wants=network.target
    #
    [Timer]
    OnCalendar=daily
    AccuracySec=1h
    Persistent=true
    #
    [Install]
    WantedBy=timers.target

    И сервис к нему sudo systemctl edit --full --force z-i-update-daily.service:

    # /etc/systemd/system/z-i-update-daily.service
    [Unit]
    Description=Zapret Info daily update service
    After=network.target
    Wants=network.target
    #
    [Service]
    Type=oneshot
    User=tinyproxy
    Group=tinyproxy
    ExecStartPre=/usr/bin/git -C /usr/local/lib/z-i pull
    ExecStart=+/usr/bin/systemctl try-restart tinyproxy.service

  6. Наконец вишенка на торте, редактируем tinyproxy.service под наши нужды — sudo systemctl edit tinyproxy.service:

    # /etc/systemd/system/tinyproxy.service.d/override.conf
    [Unit]
    Wants=network.target
    Wants=z-i-prepare.service
    After=z-i-prepare.service
    Wants=tinyproxy-cfg-generator.service
    After=tinyproxy-cfg-generator.service
    #
    [Service]
    User=tinyproxy
    Group=tinyproxy
    ExecStart=
    ExecStart=/usr/bin/tinyproxy -c /run/tinyproxy/tinyproxy.conf
    ExecStopPost=+/usr/bin/rm -rf /run/tinyproxy/tinyproxy.conf

  7. А теперь, со всем этим безобразием мы попытаемся взлететь ©

    sudo systemctl enable --now tinyproxy
    sudo systemctl enable --now z-i-update-daily.timer

Вдумчивый читатель безусловно поинтересуется, Для чего такие танцы с бубном? Что-ж, в заключении не мешает прояснить некоторые моменты. Пойдём прямо по пунктам предыдущего раздела.


  1. Тут всё просто. Мы сохраняем в отдельный файл ту часть конфигурации, которая не должна меняться автоматически.
  2. Очень важный параметр, сокращающий время загрузки основного сервиса с часа (SIC!) до, примерно, полутора минут (нетбучный AMD проц года 2009-го и HDD на 5400 об./мин.). Разумеется это не единственный способ повышения производительности.
  3. «bootstrap» юнит, который запускается всегда, но отрабатывает только в том случае если отсутствует директория /usr/local/lib/z-i/ либо пуст файл /usr/local/lib/z-i/dump.csv (Директивы Condition*). Ключик --depth 1 позволяет склонировать только последний коммит, а не все 8 Гб.
  4. Основная генерация конфига и ещё один лайвхак для повышения производительности. Из csv
    awk-ом вырезается поле с доменом, очищается от лишних символов. Удаляются строки с пустым доменом, далее cat отправляет результат на stdout., а уже юнит, благодаря директиве StandardOutput= записывает весь вывод в конфиг файл в /run на tmpfs! По зависимостям запускается после того как отработает «bootstrap» юнит из предыдущего пункта.
  5. Раз в сутки, начиная с нуля часов, с джиттером в час, обновляем репозиторий и перегенерим конфиг, с рестартом сервиса. Точнее рестартуем сервис с перегенерацией конфтига.
  6. (и 7.) Ну тут всё понятно, запуск вспомогательных юнитов и основного.

Данная связка работает уже неделю 2,5 месяца. Глюкобагов, пока, вроде-бы не замечено. готовые конфиги и скрипты живут на гитхабе, PR приветствуются!

© Habrahabr.ru