21 KiB
Прозрачный прокси контейнера через Shadowsocks и tun2socks
У меня есть Docker-контейнер Audiobookshelf, в котором я слушаю аудио-книги и подкасты. Он сам скачивает подкасты из интернета и это очень удобно (все нужные мне подкасты в одном месте). Работал контейнер на домашнем NAS Synology (в нем есть Docker Station с веб-интерфейсов). Книг у меня в "библиотеке" уже более тысячи, а подкастов и того больше, так что домашний NAS с доступом из интернет -- идеальное решение.
Все отлично работало и подкасты обновлялись, пока не настало DPI. Где-то в первой декаде января 2026 подкасты перестали обновляться. Проверки показали, что это из-за блокировок на уровне провайдера. Mp3-файлы файлы размещены на DigitalOcean, и заблокирован именно он. Audiobookshelf получает ссылки через RSS-фиды, видит новые эпизоды, и зависает при скачивании.
Как решить проблему DPI?
Как все работало до блокировок:
Старая схема
Интернет
▲
│
│
┌─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-файлы через прокси. При этом прослушивание книг и подкастов должно работать по старой схеме, без изменений.
Как-то так:
Интернет для 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 у меня было:
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
Теперь у нас будет три контейнера (и определённая зависимость между ними):
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:// и до @). Например, командой
в терминале:
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). Поэтому контейнеру нужны права
и доступ к этому устройству. Это достигается через:
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
На Synology (да и многих Linux) TUN-интерфейс /dev/net/tun обычно закрыт. Нужно будет разрешить доступ к нему:
# Проверяем, что 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". Это значит, что он будет
использовать сетевой стек контейнера tun2socks. В результате весь его трафик будет идти через TUN-интерфейс
Дальше уже tun2socks определит что с ним делать. Если трафик пришел снаружи, с порта 8000, то он попадёт
в Audiobookshelf, как и раньше. Если же сам Audiobookshelf начнет что-то скачивать, то запрос уйдет через
TUN-интерфейс, попадет в tun2socks, который отфорвардит его через SOCKS5-прокси ss-ru на Outline/VPS
(на внешнем хостинге).
Запуск и проверка
Конечно, первым делом проверить в интерфейсе Docker Station, что проект "зелененький" и все контейнеры тоже "зелененькие". Ну или выполнить:
sudo docker ps
И увидим что-то типа:
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:
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:
sudo docker logs audiobookshelf --tail 100
...и Google и ИИ-ассистенты в помощь. Node.js -- это не самая простая среда для отладки.