Sign inSign up

edwardfuchs/webproxy

By edwardfuchs

•Updated 3 months ago

Multi-protocol TCP/HTTP(S) proxy with web UI: forward/SNI/Host, ports added live, no restart

Image
0

10K+

edwardfuchs/webproxy repository overview

⁠webproxy

Многоцелевой TCP-прокси с веб-настройкой. Без внешних зависимостей (чистый Python stdlib).

Один процесс проксирует много таргетов, правила добавляются на лету через веб-панель — без перезапуска контейнера. Конфиг хранится в JSON-томе.

⁠Режимы правил

РежимДля чегоКак маршрутизирует
forwardлюбой TCP-протокол: ldap/ldaps, ssh, БД, http(s) на нестандартном портуодин порт → один host:port, сырой passthrough
tlsмного HTTPS-сайтов на одном порту (напр. 443)по имени из TLS ClientHello (SNI)
httpмного HTTP-сайтов на одном порту (напр. 80)по заголовку Host
connectHTTP forward-прокси: ставится в HTTPS_PROXY клиентапринимает CONNECT на любой хост, уводит через via

forward протокол-независим — прокси не разбирает содержимое, просто гоняет байты. tls/http нужны только чтобы посадить много сайтов одного протокола на один порт.

⁠Запуск

services:
  proxy:
    image: edwardfuchs/webproxy:latest
    # host-сеть обязательна: порты добавляются динамически через панель,
    # пробросить их через -p заранее нельзя. Все открытые порты сразу на хосте.
    network_mode: host
    restart: unless-stopped
    environment:
      ADMIN_HOST: "0.0.0.0"     # адрес веб-панели
      ADMIN_PORT: "8088"        # порт веб-панели
      ADMIN_PASSWORD: ""        # если задан — включается Basic Auth. Обязательно для хоста в интернете!
      BIND_HOST: "0.0.0.0"      # на каком адресе слушать проксируемые порты
    volumes:
      - ./data:/data            # config.json переживает пересоздание контейнера
docker compose up -d
# панель: http://<хост>:8088

Или напрямую:

docker run -d --network host -v "$PWD/data:/data" \
  -e ADMIN_PASSWORD=секрет edwardfuchs/webproxy:latest

⁠Переменные окружения

ПеременнаяПо умолчаниюНазначение
ADMIN_HOST0.0.0.0Адрес, на котором слушает веб-панель. Поставь 127.0.0.1, чтобы спрятать её и ходить через SSH-туннель.
ADMIN_PORT8088Порт веб-панели.
ADMIN_PASSWORD(пусто)Если задан — включается HTTP Basic Auth на панель. Обязательно, если хост смотрит в интернет: с host-сетью панель торчит наружу.
BIND_HOST0.0.0.0Адрес, на котором открываются проксируемые порты.
CONFIG_PATH/data/config.jsonПуть к файлу конфигурации внутри контейнера.

⁠Формат маршрутов (для режимов tls / http)

В поле «Маршруты» веб-панели — по строке домен = host:port:

site1.com    = 10.0.0.1:443
*.site2.com  = 10.0.0.2:443
_            = 10.0.0.9:443     # строка с _ — маршрут по умолчанию

⁠Цепочка через вышестоящий прокси (upstream)

У правила есть необязательное поле via — тогда трафик идёт к цели не напрямую, а через вышестоящий прокси. TLS остаётся сквозным (сертификат приходит настоящий от цели):

via: socks5://user:pass@upstream:1080   # SOCKS5 (+ socks5h / socks4 / socks4a)
via: http://user:pass@proxy:8080        # HTTP CONNECT (+ https:// — TLS до прокси)

Работает во всех режимах. Пример «сайт через SOCKS5» (клиент резолвит имя в прокси, а до цели идём через upstream):

порт: 443, режим: tls
маршрут: huggingface.co = $sni:443     # $sni — форвардить на имя из SNI
via: socks5://user:pass@upstream:1080

$sni (и $host для http-режима) в таргете = имя из SNI/Host: одно правило обслуживает любой сайт без перечисления, а адрес цели резолвит upstream.

⁠HTTP forward-прокси (режим connect) + huggingface

Режим connect делает из webproxy обычный HTTP-прокси, который ловит любой хост (включая редиректы huggingface на CDN/Xet) и уводит через via:

правило: режим connect, порт 8888, via: socks5://user:pass@upstream:1080
export HTTPS_PROXY=http://<proxy-host>:8888
export HTTP_PROXY=http://<proxy-host>:8888
export HF_HUB_ENABLE_HF_TRANSFER=0     # иначе Rust-загрузчик игнорит прокси и виснет на 0B
export HF_HUB_DISABLE_XET=1
huggingface-cli download <repo> <file>

HF_ENDPOINT для этого не нужен: он меняет только хост API, а файлы качаются по 302-редиректу с CDN-хоста — его ловит именно транспортный прокси (HTTPS_PROXY / режим connect), а не смена endpoint.

⁠Лог соединений

Каждое соединение пишется строкой — видно, куда прокси его отправил и на какой реальный IP приконнектился (полезно для диагностики маршрутизации по SNI/Host):

[conn] :636 tls SNI='dc01.my.home' client=10.0.0.5 -> target=dc01.my.home:636 peer=10.0.0.1:636
[proxy] :636 нет маршрута для SNI='dcXX.my.home'

нет маршрута ... означает, что клиент прислал SNI/Host, не совпавший ни с одним маршрутом (или не прислал вовсе) — соединение закрывается, клиент видит SSL: UNEXPECTED_EOF_WHILE_READING. Добавь совпадающий маршрут или дефолт _ = host:port.

⁠Важно

  • host-сеть = нет сетевой изоляции и работает на Linux (в Docker Desktop на Mac/Windows урезана). Это плата за динамические порты.
  • Файлы в томе создаются от root (контейнер работает под root — чтобы слушать порты < 1024: 80, 443, 389, 636…).
  • L4-прокси: TLS не терминируется, сертификаты остаются на бэкендах; SNI-режим лишь читает имя, не расшифровывая трафик.

Tag summary

Content type

Image

Digest

sha256:e9f900bda…

Size

41.2 MB

Last updated

3 months ago

docker pull edwardfuchs/webproxy