diff --git a/docker/docker-proxy-container-via-shadowsocks-and-tun2socks.md b/docker/docker-proxy-container-via-shadowsocks-and-tun2socks.md index 95a24da..b20f51f 100644 --- a/docker/docker-proxy-container-via-shadowsocks-and-tun2socks.md +++ b/docker/docker-proxy-container-via-shadowsocks-and-tun2socks.md @@ -1,20 +1,20 @@ -# Прозрачный прокси контейнера через Shadowsocks и tun2socks +# Прозрачный прокси контейнера через Shadowsocks и tun2socks -![Static Badge](https://img.shields.io/badge/очень_полезно-99.9%-green) +![Static Badge](https://img.shields.io/badge/ОЧЕНЬ_ПОЛЕЗНО-99.9%-blue) +[![Static Badge](https://img.shields.io/badge/ТИПОГРАФИКА-etpgrf-green)](https://typograph.cube2.ru/) -У меня есть Docker-контейнер Audiobookshelf, в котором я слушаю аудиокниги и подкасты. Он сам скачивает подкасты - из интернета, и это очень удобно (все нужные мне подкасты в одном месте). Работал контейнер на домашнем NAS Synology - (в нем есть Docker Station с веб-интерфейсом). Книг у меня в "библиотеке" уже более тысячи, а подкастов и того больше, - так что домашний NAS с доступом из интернета — идеальное решение. +У меня есть Docker-контейнер Audiobookshelf, в котором я слушаю аудиокниги и подкасты. Он сам скачивает подкасты + из интернета, и это очень удобно (все нужные мне подкасты в одном месте). Работал контейнер на домашнем NAS Synology + (в нем есть Docker Station с веб-интерфейсом). Книг у меня в «библиотеке» уже более тысячи, а подкастов и того больше, + так что домашний NAS с доступом из интернета — идеальное решение. -Все отлично работало и подкасты обновлялись, пока не настал DPI. Где-то в первой декаде января 2026 года подкасты перестали - обновляться. Проверки показали, что это из-за блокировок на уровне провайдера. Mp3-файлы размещены на - DigitalOcean, и заблокирован именно он. Audiobookshelf получает ссылки через RSS-фиды, видит новые эпизоды и зависает - при скачивании. +Все отлично работало и подкасты обновлялись, пока не настал DPI. Где-то в первой декаде января 2026 года подкасты перестали + обновляться. Проверки показали, что это из-за блокировок на уровне провайдера. Mp3-файлы размещены на DigitalOcean, и заблокирован именно он. Audiobookshelf получает ссылки через RSS-фиды, видит новые эпизоды и зависает + при скачивании. ## Как решить проблему DPI? -Как все работало до блокировок: +Как всё работало до блокировок: ### Старая схема @@ -35,22 +35,21 @@ └───────────────────────────┘ ``` -Контейнер 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 на 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. +Скачивание подкастов внутри Audiobookshelf идет напрямую. Контейнер сразу обращается к Роутеру и пытается скачать + mp3-файл. И это ему не удаётся из-за DPI. ### Новая схема -К счастью, проверки показали, что у провайдеров (даже у российских) нет блокировок DigitalOcean. А значит, эпизоды - подкастов можно скачать на VPS/VDS-виртуалке, и нужно только настроить Audiobookshelf так, чтобы он скачивал mp3-файлы - через прокси. При этом прослушивание книг и подкастов должно работать по старой схеме, без изменений. +К счастью, проверки показали, что у провайдеров (даже у российских) нет блокировок DigitalOcean. А значит, эпизоды + подкастов можно скачать на VPS/VDS-виртуалке, и нужно только настроить Audiobookshelf так, чтобы он скачивал mp3-файлы + через прокси. При этом прослу­шивание книг и подкастов должно работать по старой схеме, без изменений. -Как-то так: +Как-то так: ```text Интернет для скачивания Интернет для прослушивания ▲ ▲ @@ -81,20 +80,18 @@ Traefik на k3s и все еще проходит через VIP-адрес Kee Почему так сложно? - - **Во-первых**, на VPS/VDS, где я держу свои проекты, уже был настроен Shadowsocks-сервер (Outline), не для обхода - блокировок, а для безопасного доступа и администрирования (а еще для того, чтобы посещать Госуслуги и другие ресурсы, - находясь в поездках за границей). Поэтому было логично использовать его. - - **Во-вторы**х, для Docker не существует полноценного VPN-клиента Shadowsocks. Есть только прокси-клиенты (ss-local), - которые предоставляют SOCKS5-интерфейс. - - При этом — и это **в-третьих** — Audiobookshelf не умеет работать с SOCKS5. Ему нужен настоящий HTTP, причем из-за - встроенной в Audiobookshelf защиты от SSRF-атак он не может работать даже с HTTP-прокси, который находится - в Docker-сети (например, Privoxy). Ему нужно сделать прозрачную сеть через TUN-интерфейс — тогда он будет + - **Во-первых**, на 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 уже нет + - Для этого, **в-четвертых**, нужно сделать так, чтобы весь трафик контейнера `Audiobookshelf` автома­тически + шел через единственный контейнер (`tun2socks`), который будет форвардить и http, и https через Shadowsocks (для скачивания подкастов) и отдавать http-трафик на порт 8000 для прослу­шивания через Nginx. Именно такая схема + не будет ломать работу SSRF-фильтра и защитит от этого типа атаки (_можно было, конечно, откатить Audiobookshelf + до древней версии 2.6.0, где дыра SSRF еще была, но это не самое безопасное решение, да и на DockerHub уже нет этой версии_). ### Старый манифест Docker @@ -116,18 +113,18 @@ services: audiobookshelf: - TZ=Europe/Moscow ``` -Тут, вроде, всё просто. На Synology есть папка `/volume1/music/` уже с завода. В ней я создал папки `_podcasts` - и `_audio_books` для хранения книг и подкастов и смонтировал эти папки в контейнер. Также добавил папки `config` - и `metadata` для хранения конфигурации и метаданных. Эти папки создаются при первом запуске контейнера и будут - расположены рядом с `compose.yml` (в каталоге `/volume1/docker/audiobookshelf/`). Папка `/volume1/docker/` тоже - будет создана при установке приложения Docker Station на Synology; нам нужно только создать каталог `audiobookshelf/` - и выбрать его при создании проекта в Docker Station. +Тут, вроде, всё просто. На 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 и так далее. +Порт 8000 проброшен наружу, и Nginx на хосте Orange Pi 5 Plus проксирует на этот порт, добавляет SSL и так далее. -### Новая схема и манифест для Docker Station +### Новая схема и манифест для Docker Station -Теперь у нас будет три контейнера (и определённая зависимость между ними): +Теперь у нас будет три контейнера (и определённая зависимость между ними): ```yml version: "3.8" @@ -184,28 +181,27 @@ services: Что тут происходит: -**Первым стартует** контейнер `ss-ru`. Он ни от кого не зависит. Он подключается к моему Outline-серверу и предоставляет - SOCKS5-прокси на порту `1080`. +**Первым стартует** контейнер `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://` и `@`). Например, командой - в терминале: +Где брать параметры для подключения к 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]` — это `пароль`. +Получим строку вида `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`. Он зависит от `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 @@ -213,7 +209,8 @@ services: - /dev/net/tun ``` -На Synology (да и на многих Linux-системах) TUN-интерфейс `/dev/net/tun` обычно закрыт. Нужно будет разрешить доступ к нему: +На Synology (да и на многих Linux-системах) TUN-интерфейс `/dev/net/tun` обычно закрыт. Нужно будет +разрешить доступ к нему: ```bash # Проверяем, что TUN доступен ls /dev/net/tun @@ -226,18 +223,17 @@ ls /dev/net/tun | Заметим | | ------- | -| Я пробовал другие решения: (1) `Privoxy` — Проблемы: axios внутри Audiobookshelf плохо работает с прокси, ломаются редиректы, SSRF-фильтр; сами RSS загружаются, но SSRF будет блокировать запросы к iTunes, а значит, не будут получаться метаданные, а без метаданных и mp3-файлы не скачиваются. (2) `redsocks` — Проблемы: нестабильный iptables redirect (на Synology это вообще не работает, похоже), падение самого redsocks, сложная настройка NAT внутри Docker. +| Я пробовал другие решения: (1) `Privoxy` — Проблемы: axios внутри Audiobookshelf плохо работает с прокси, ломаются редиректы, 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 (на внешнем хостинге). +**Третьим стартует** контейнер `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, что проект «зелененький» и все контейнеры тоже «зелененькие». Ну или выполнить: @@ -253,71 +249,71 @@ CONTAINER ID IMAGE COMMAND CREATED 3bdb3269ed77 shadowsocks/shadowsocks-libev "ss-local -s 90.156.…" 16s ago Up 16s ss-ru ``` -Для проверки того, что тоннель работает и подкасты скачиваются, выполним в терминале на Synology: +Для проверки того, что тоннель работает и подкасты скачиваются, выполним в терминале на 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-скрипт для проверки скачивания файла. +Audiobookshelf работает на Node.js, а внутри контейнера нет ни `curl`, ни `wget`. Так что придется запустить такой + заковыристый js-скрипт для проверки скачивания файла. -В результате мы получим `test.mp3` в папке `/podcasts/` (а она у нас снаружи контейнера -в `/volume1/music/_podcasts`). Это выпуск №1074 подкаста «Аэростат» с альбомом Аквариума «Странные Новости +В результате мы получим `test.mp3` в папке `/podcasts/` (а она у нас снаружи контейнера +в `/volume1/music/_podcasts`). Это выпуск №1074 подкаста «Аэростат» с альбомом Аквариума «Странные Новости с Далёкой Звезды» (2026 год, очень рекомендую послушать). Этот подкаст размещен на DigitalOcean, так что если он скачался, значит туннель работает и обход блокировок провайдера сработал. -Проверить, что метаданные из iTunes тоже подгружаются, можно, только настроив автообновление подкастов - в интерфейсе Audiobookshelf (например, через пять минут). Но получение метаданных происходит "под капотом", - и иначе это никак не диагностировать. +Проверить, что метаданные из iTunes тоже подгружаются, можно, только настроив автообно­вление подкастов + в интерфейсе Audiobookshelf (например, через пять минут). Но получение метаданных происходит «под капотом», + и иначе это никак не диагно­стировать. -Должно всё работать. Но если не работает, то "курите логи" контейнера `audiobookshelf`: +Должно всё работать. Но если не работает, то «курите логи» контейнера `audiobookshelf`: ```bash sudo docker logs audiobookshelf --tail 100 ``` -...и Google и ИИ-ассистенты в помощь. Node.js — это не самая простая среда для отладки. +...и Google и ИИ-ассистенты в помощь. Node.js — это не самая простая среда для отладки. -## P.S. Важное замечание о защите Audiobookshelf от SSRF +## P. S. Важное замечание о защите Audiobookshelf от SSRF -Во время настройки в логах можно столкнуться с ошибкой вида: `Call to 172.25.0.3 is blocked` или `invalid feed payload`. -Это не ошибка сети и не проблема RSS. Это встроенная защита Audiobookshelf от SSRF-атак. +Во время настройки в логах можно столкнуться с ошибкой вида: `Call to 172.25.0.3 is blocked` или `invalid feed payload`. +Это не ошибка сети и не проблема RSS. Это встроенная защита Audiobookshelf от SSRF-атак. ### Что такое SSRF -SSRF (Server Side Request Forgery) — это тип атаки, когда сервер заставляют делать запросы в свою внутреннюю сеть. -Например, злоумышленник может подложить RSS-фид со ссылкой на внутренний сервис. Например: `http://127.0.0.1:8080/admin` +SSRF (Server Side Request Forgery) — это тип атаки, когда сервер заставляют делать запросы в свою внутреннюю сеть. +Например, злоумы­шленник может подложить RSS-фид со ссылкой на внутренний сервис. Например: `http://127.0.0.1:8080/admin` или `http://172.25.0.3:5000`. Если сервер загрузит такой URL, атакующий потенциально сможет: - читать данные внутренних сервисов - получать внутренние токены авторизации - - обращаться к Docker API + - обращаться к Docker API - сканировать внутреннюю сеть -Поэтому многие современные приложения блокируют такие запросы. И Audiobookshelf не исключение. +Поэтому многие современные приложения блокируют такие запросы. И Audiobookshelf не исключение. -Audiobookshelf использует библиотеку `ssrf-req-filter`. Она проверяет IP-адрес, в который разрешается (резолвится) домен, и блокирует некоторые диапазоны: +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-сети. +То есть любые внутренние адреса, включая адреса Docker-сети. -Когда использовалась схема `Audiobookshelf → HTTP Proxy → Shadowsocks`, то HTTP-прокси находился в Docker-сети. Например: +Когда исполь­зовалась схема `Audiobookshelf → HTTP Proxy → Shadowsocks`, то HTTP-прокси находился в Docker-сети. Например: `privoxy:8118` или `redsocks:12345`. -После DNS-разрешения (резолва) запрос фактически отправлялся на адрес контейнера прокси внутри Docker-сети. Например, `172.25.0.3`. И SSRF-фильтр Audiobookshelf это видел и блокировал запрос. Именно поэтому в логах появлялось: `Call to 172.25.0.3 is blocked`. +После DNS-разрешения (резолва) запрос фактически отправлялся на адрес контейнера прокси внутри Docker-сети. Например, `172.25.0.3`. И SSRF-фильтр Audiobookshelf это видел и блокировал запрос. Именно поэтому в логах появлялось: `Call to 172.25.0.3 is blocked`. ### Почему HTTP_PROXY часто ломает Audiobookshelf -Audiobookshelf использует библиотеку axios. А он: - - плохо работает с редиректами через прокси - - иногда разрешает (резолвит) DNS на стороне прокси - - иногда разрешает (резолвит) DNS локально +Audiobookshelf использует библиотеку axios. А он: + - плохо работает с редиректами через прокси + - иногда разрешает (резолвит) DNS на стороне прокси + - иногда разрешает (резолвит) DNS локально -В результате: часть запросов проходит, часть падает, RSS может читаться, но mp3 не скачиваются и так далее. +В результате: часть запросов проходит, часть падает, RSS может читаться, но mp3 не скачиваются и так далее. -Опытным путем я определил, что наиболее надёжный способ — сетевой туннель (`tun2socks`), при котором приложение не знает о проксировании вообще. +Опытным путем я определил, что наиболее надёжный способ — сетевой туннель (`tun2socks`), при котором приложение не знает о прокси­ровании вообще.