# Прозрачный прокси контейнера через Shadowsocks и tun2socks У меня есть Docker-контейнер Audiobookshelf, в котором я слушаю аудио-книги и подкасты. Он сам скачивает подкасты из интернета и это очень удобно (все нужные мне подкасты в одном месте). Работал контейнер на домашнем NAS Synology (в нем есть Docker Station с веб-интерфейсов). Книг у меня в "библиотеке" уже более тысячи, а подкастов и того больше, так что домашний NAS с доступом из интернет -- идеальное решение. Все отлично работало и подкасты обновлялись, пока не настало DPI. Где-то в первой декаде января 2026 подкасты перестали обновляться. Проверки показали, что это из-за блокировок на уровне провайдера. Mp3-файлы файлы размещены на DigitalOcean, и заблокирован именно он. Audiobookshelf получает ссылки через RSS-фиды, видит новые эпизоды, и зависает при скачивании. ## Как решить проблему DPI? Как все работало до блокировок: ### Старая схема ```text Интернет ▲ │ │ ┌─Docker (на Synology)──────┐ скачивание подкастов ┌────┴─────┐ │ Audiobookshelf ├─────────────────────────────►│ Роутер │ └──────────────────┬────────┘ └────▲─────┘ │ │ │ для прослушивания │ │ (порт 8000) │ │ │ ┌─Host (Orange Pi)─▼────────┐ для прослушивания c SSL │ │ Nginx Proxy ├───────────────────────────────────┘ └───────────────────────────┘ ``` Контейнер Audiobookshelf на Synology работает по 8000 порту. Nginx на Orange Pi 5 Plus проксирует на этот порт на IP-адресе Synology, добавляет SSL и обновляет сертификат Let's Encrypt (на самом деле у меня для этой цели используется Traefik на k3s и все еще проходдит через VIP-адрес Keepalive, но для простоты я опускаю эти детали). На роутере настройка проброс 443-порта на Orange Pi. Внешний DNS настроен на IP-адрес роутера (A- и AAAA-записи) и так я могу слушать подкасты из любой точки мира. Скачивание подкастов внутри Audiobookshelf идет напрямую. Контейнер сразу обращается к Роутеру и пытается скачать mp3-файл. И это ему не удаётся из-за DPI. ### Новая схема К счастью, проверки показали, что у провайдеров (даже у Российских) нет блокировок DigitalOcean. А значит эпизоды подкастов можно скачать на VPS/VDS виртуалке, и нужно только настроить Audiobookshelf так, чтобы он скачивал mp3-файлы через прокси. При этом прослушивание книг и подкастов должно работать по старой схеме, без изменений. Как-то так: ```text Интернет для upload Интернет для прослушивания ▲ ▲ │ │ │ │ ┌─VPS/VDS (Hosting)────────────┐ │ для прослушивания │ VPN (Shadowsocks Server) │ │ (SSL) └──────────────────────▲───────┘ │ ╚═════════════════════╗ │ ┌──╨────┴──┐ │ Роутер │ └──▲────▲──┘ ║ │ для upload ║ └──────────────┐ (VPN-тоннель) ║ │ ···Docker Compose (на Synology)································ ║ ·············· │ : ║ : │ : ┌────────────────┐ ┌───────────────┐ ┌─────────╨──────────┐ : ▲ : │ Audiobookshelf ├─────►│ tun2socks ├────────►│ shadowsocks/SOCKS5 │ : │ : └────────────────┘ └───────┬───────┘ └────────────────────┘ : │ : │ для прослушивания : │ ··································· │ ·········································· │ │ (порт 8000) ▲ ┌─Host (Orange Pi)─▼────────┐ для прослушивания c SSL │ │ Nginx Proxy ├──────────────────────────────────────┘ └───────────────────────────┘ ``` Почему так сложно? - Во-первых, на VPS/VDS, где я держу свои проекты, уже был настроен Shadowsocks-сервер (Outline), Не для обхода блокировок, а для безопасного доступа и администрирования (а еще для того чтоб посещать Госуслуги и другие ресурсы, находясь в поездках за границей). Поэтому было логично использовать его. - Во-вторых, для Docker не существует полноценного VPN-клиента Shadowsocks. Есть только прокси-клиенты (ss-local), которые предоставляют SOCKS5 интерфейс. - При этом, и это в-третьих, Audiobookshelf не умеет работать с SOCKS5. Ему нужен настоящий HTTP, причем из-за встроенной в Audiobookshelf защиты от SSRF атак он не может работать даже с HTTP-прокси, который находится в Docker сети (например, Privoxy). Ему нужно сделать прозрачную сеть через TUN интерфейс -- тогда он будет доволен. - Для этого, в-четвертых, нужно сделать так, чтобы весь трафик контейнера Audiobookshelf автоматически шел через единственный контейнер (tun2socks), который будет форвардить и http, и https через Shadowsocks (для скачивания подкастов) и отдавать http-трафик на порт 8000 для прослушивания через Nginx. Именно такая схема не будет ломать работу SSRF-фильтра и защитит от этого тип атаки (_можно было, конечно, откатить Audiobookshelf до древней версии 2.6.0, где дыра SSRF еще была, но это не самое безопасное решение, да и на DockerHub уже нет этой версии_). ### Старый манифест Docker В Docker Station на Synology у меня было: ```yml version: "3.8" services: audiobookshelf: image: advplyr/audiobookshelf:latest restart: always ports: - 8000:80 volumes: - ./config:/config - ./metadata:/metadata - /volume1/music/_podcasts:/podcasts - /volume1/music/_audio_books:/audiobooks environment: - TZ=Europe/Moscow ``` Тут, вроде все просто. На Synology есть папка `/volume1/music/` уже с завода. В ней я создал папки `_podcasts` и `_audio_books` для хранения книг и подкастов, и смонтировал эти папки в контейнер. Также добавил папки `config` и `metadata` для хранения конфигурации и метаданных. Эти папки создадются при первом запуске контейнера, и будут расположены рядом `compose.yml` (в каталоге `/volume1/docker/audiobookshelf/`... папка `/volume1/docker/` тоже будет создана при установке приложения Docker Station на Synology... нам нужно только создать каталог `audiobookshelf/` и и выбрать ее при создании проекта в Docker Station). Порт 8000 проброшен наружу и Nginx на хосте Orange Pi 5 Plus проксирует на этот порт, добавляет SSL и так далее. ### Новая схема и манифест для Docker Station Теперь у нас будет три контейнера (и определённая зависимость между ними): ```yml version: "3.8" services: # [1] Shadowsocks клиент для подключения к Outline/VPS (предоставляет SOCKS5 на порту 1080) ss-ru: image: shadowsocks/shadowsocks-libev container_name: ss-ru restart: unless-stopped command: > ss-local -s [IP-адрес Outline/VPS] -p [порт Outline/VPS] -k [пароль Outline/VPS] -m [метод шифрования] -l 1080 -b 0.0.0.0 # [2] Privoxy — HTTP-прокси для Node.js, форвардит через ss-ru tun2socks: image: xjasonlyu/tun2socks container_name: tun2socks restart: unless-stopped cap_add: - NET_ADMIN devices: - /dev/net/tun ports: - "8000:80" command: > -device tun://tun0 -proxy socks5://ss-ru:1080 depends_on: - ss-ru # [3] Audiobookshelf, использующий Privoxy для доступа к интернету audiobookshelf: image: advplyr/audiobookshelf:latest container_name: audiobookshelf restart: always network_mode: "service:tun2socks" volumes: - ./config:/config - ./metadata:/metadata - /volume1/music/_audio_books:/audiobooks - /volume1/music/_podcasts:/podcasts environment: - TZ=Europe/Moscow depends_on: tun2socks: condition: service_started ``` Что тут происходит: *Первым стартует** контейнер `ss-ru`. Он ни от кого не зависит. Он подключается к моему Outline-серверу и предоставляет SOCKS5-прокси на порту `1080`. Где брать параметры для подключения к Outline/VPS? В панели Outline Manager "Add new Key". Она предоставит строку вида: `ss://[зашифрованный-base64-ключ--метод-шифрования:пароль]@[IP-адрес Outline/VPS]:[порт Outline/VPS]/?outline=1`. Параметры `[IP-адрес Outline/VPS]` и `[порт Outline/VPS]` нужно взять непосредственно из этой строки. Метод шифрования и пароль нужно расшифровать из первой части строки (часть строки между `ss://` и до `@`). Например, командой в терминале: ```shell echo "Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpDYVF6OERMRDVHd09meTRvVVk1TmxK" | base64 -d ``` Получим строку вида `chacha20-ietf-poly1305:пароль`. Таким образом `[метод шифрования]` — это `chacha20-ietf-poly1305`, а `[пароль Outline/VPS] — это `пароль`. **Вторым стартует** контейнер `tun2socks`. Он зависит от `ss-ru`, так что гарантированно запустится после него. Он использует `ss-ru` как SOCKS5-прокси и создает TUN-интерфейс `tun0` внутри контейнера. Так же он предоставляет HTTP-интерфейс на порту 80 внутри нашего проекта Docker Station (с него будет отвечать Audiobookshelf) который проброшен наружу как 8000 (его отпроксирует Nginx на хосте Orange Pi 5 Plus). Контейнер tun2socks использует системный TUN-интерфейс Synology (`/dev/net/tun`). Поэтому контейнеру нужны права и доступ к этому устройству. Это достигается через: ```yml cap_add: - NET_ADMIN devices: - /dev/net/tun ``` На Synology (да и многих Linux) TUN-интерфейс `/dev/net/tun` обычно закрыт. Нужно будет разрешить доступ к нему: ```bash # Проверяем, что TUN доступен ls /dev/net/tun # Если выдаёт "/dev/net/tun: no such device", то нужно создать устройство sudo modprobe tun # И снова проверить, что устройство появилось ls /dev/net/tun # Должно вывести: "/dev/net/tun" ``` | Заметим | | ------- | | Я пробовал други решения: (1) `Privoxy` -- Проблемы: axios внутри Audiobookshelf плохо работает с proxy, ломаются редиректы,SSRF фильтр, сами RSS загружаются, но SSRF будет блокировать запросы к iTunes, а, значит, не будут получаться метаданные, а без метаданных и mp3-файлы не скачиваются. (2) `redsocks` -- Проблемы: нестабильный iptables redirect (на Synology это вообще не работает, похоже), падение самого redsocks, сложная настройка NAT внутри Docker. **Третьим стартует** контейнер `audiobookshelf`. В нём почти все по старому. Но теперь он зависит от `tun2socks`, так что гарантированно запустится после него. Кроме того, у него нет проброса портов `8000:80` (они у нас теперь в контейнере tun2socks). Вместо этого у него `network_mode: "service:tun2socks"`. В целом, `network_mode: "service:...` -- это мощная, но специфичная функция Docker, которая 'склеивает' сетевые стеки контейнеров в один, а это значит, что контейнер `audiobookshelf` будет использовать сетевой стек контейнера `tun2socks` и в результате весь трафик будет идти через TUN-интерфейс. Дальше уже сам `tun2socks` определит что с ним делать. Если трафик пришел снаружи, с порта 8000, то он попадёт в Audiobookshelf, как и раньше, а если же сам Audiobookshelf начнет что-то скачивать, то запрос уйдет через TUN-интерфейс, попадет в `tun2socks`, который отфорвардит его через SOCKS5-прокси `ss-ru` на Outline/VPS (на внешнем хостинге). ## Запуск и проверка Конечно, первым делом проверить в интерфейсе Docker Station, что проект "зелененький" и все контейнеры тоже "зелененькие". Ну или выполнить: ```bash sudo docker ps ``` И увидим что-то типа: ```text CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8599f59103eb advplyr/audiobookshelf:2.28.0 "tini -- node index.…" 16s ago Up 17s audiobookshelf 038ea5878848 xjasonlyu/tun2socks "/entrypoint.sh -dev…" 16s ago Up 16s 0.0.0.0:8000->80/tcp, :::8000->80/tcp tun2socks 3bdb3269ed77 shadowsocks/shadowsocks-libev "ss-local -s 90.156.…" 16s ago Up 16s ss-ru ``` Для проверки, что тоннель работает и подкасты скачиваются выполним в терминале на Synology: ```bash sudo docker exec -it audiobookshelf sh -c " node -e \"const https=require('https');const fs=require('fs');const file=fs.createWriteStream('/podcasts/test.mp3');https.get('https://aerostats.getmobileup.com/music/1074.mp3',res=>{res.pipe(file);file.on('finish',()=>console.log('Done'))})\"" ``` Audiobookshelf работает на Node.js, а внутри контейнера нет ни `curl`. ни `wget`. Так что придется запустить такой заковыристый js-скрипт для проверки скачивания файла. В результате должен получим `test.mp3` в папке `/podcasts/` (а она у нас снаружи контейнера в `/volume1/music/_podcasts`). Это выпуск №1074 подкаста "Аэростат" с альбомом Аквариума "Странные Новости с Далёкой Звезды" (2026 год, очень рекомендую послушать). Этот подкаст размещен на DigitalOcean, так что если он скачался, значит туннель работает и обход блокировок провайдера сработал. Проверить что метаданные из iTunes тоже подгружаются, можно проверить только настроив автообновление подкастов в интерфейсе Audiobookshelf (например через ять минут). К получение метаданных происходит "под капотом" и иначе никак не диагностируется. Должно все работать. Но если не работает, то "курите логи" контейнера `audiobookshelf`: ```bash sudo docker logs audiobookshelf --tail 100 ``` ...и Google и ИИ-ассистенты в помощь. Node.js -- это не самая простая среда для отладки. ## P.S. Важное замечание о защите Audiobookshelf от SSRF Во время настройки, в логах, можно столкнуться с ошибкой вида: `Call to 172.25.0.3 is blocked` или `invalid feed payload`. Это не ошибка сети и не проблема RSS. Это встроенная защита Audiobookshelf от SSRF атак. ### Что такое SSRF SSRF (Server Side Request Forgery) — это тип атаки, когда сервер заставляют делать запросы во внутреннюю сеть. Например злоумышленник может подложить RSS-фид c ссылкой на внутренний сервис. Например: `http://127.0.0.1:8080/admin` или `http://172.25.0.3:5000`. Если сервер загрузит такой URL, атакующий потенциально сможет: - читать внутренние сервисы - получать токены - обращаться к Docker API - сканировать внутреннюю сеть Поэтому многие современные приложения блокируют такие запросы. И Audiobookshelf не исключение. Audiobookshelf использует библиотеку `ssrf-req-filter`. Она проверяет IP адрес, в который резолвится домен, и блокирует некоторые диапазоны: - `127.0.0.0/8` - `10.0.0.0/8` - `172.16.0.0/12` - `192.168.0.0/16` - `169.254.0.0/16` То есть любые внутренние адреса. Включая адреса Docker сети. Когда использовалась схема `Audiobookshelf → HTTP Proxy → Shadowsocks` то HTTP proxy находился в Docker сети. Например: `privoxy:8118` или `redsocks:12345` После DNS-резолва запрос фактически отправлялся на адрес контейнера прокси внутри Docker сети. Например, `172.25.0.3`. И SSRF-фильтр Audiobookshelf это видел и блокировал запрос. Именно поэтому в логах появлялось: `Call to 172.25.0.3 is blocked`. ### Почему HTTP_PROXY часто ломает Audiobookshelf Audiobookshelf использует библиотеку axios. А он: - плохо работает с редиректами через прокси - иногда резолвит DNS на стороне прокси - иногда резолвит DNS локально В результате: часть запросов проходит; часть падает; RSS может читаться, но mp3 не скачиваются и так далее. Опытным путем я определил, что наиболее надёжный способ — сетевой туннель (`tun2socks`), при котором приложение не знает о прокси вообще.