Управление туннелями — рычаги администратора¶
Описание механизмов, которые даёт платформа для блокировки отдельного туннеля: что происходит внутри при каждом действии, как состояние переживает рестарты и какой уровень контроля даёт каждый инструмент.
Цепочка «заморозки» туннеля¶
Когда администратор нажимает «заморозить» туннель пользователя в админке, система последовательно выполняет три шага.
Шаг 1 — Источник истины: база данных (PostgreSQL)¶
TunnelController.java обновляет поле tunnel_frozen_by в таблице users:
Это единственный источник истины — он переживёт перезапуск любого контейнера. Поле может принимать три значения:
| Значение | Кто заморозил |
|---|---|
none |
Туннель активен |
user |
Пользователь сам выключил туннель |
admin |
Заморожен администратором — пользователь не может разморозить |
Шаг 2 — Оперативный кэш прокси: ETS-таблица (Elixir)¶
Сразу после записи в БД modulauth делает внутренний HTTP-вызов на dixu_proxy:
Elixir-прокси вписывает slug в ETS-таблицу :frozen_tunnels (FrozenRegistry).
ETS — это in-memory key-value хранилище внутри Erlang VM, работающее как
атомарный hashmap в RAM. Поиск по ней занимает наносекунды при любом трафике.
# FrozenRegistry.ex
def freeze(slug, by \\ "admin"), do: :ets.insert(@table, {slug, by})
def unfreeze(slug), do: :ets.delete(@table, slug)
def frozen_by(slug) do
case :ets.lookup(@table, slug) do
[{_, by}] -> by
[] -> "none"
end
end
Шаг 3 — Каждый входящий запрос к туннелю¶
При любом HTTP-запросе на slug.dix.su прокси первым делом проверяет ETS:
# proxy_controller.ex
frozen_by = DixuProxy.FrozenRegistry.frozen_by(device_slug)
if frozen_by != "none" do
html = if frozen_by == "admin",
do: frozen_admin_html(device_slug), # «заморожено администратором»
else: frozen_user_html(device_slug) # «отключено владельцем»
# возвращает 503
Посетитель получает HTML-страницу вместо содержимого туннеля.
Активные соединения не убиваются
Freeze блокирует новые запросы, но не разрывает уже установленное WebSocket-соединение device_client'а. Устройство продолжает быть подключено — новый трафик через него просто не проходит.
Персистентность при перезапуске прокси¶
При каждом старте dixu_proxy восстанавливает ETS из БД:
Modulauth возвращает все slug'и с tunnel_frozen_by != 'none'.
Elixir инициализирует :frozen_tunnels из этого списка.
Заморозка выдержит перезапуск, обновление, пересборку контейнера.
Схема потоков данных¶
Администратор (Админка)
│
▼ POST /api/admin/tunnel/{id}/freeze
modulauth (Java/Spring)
│ UPDATE users SET tunnel_frozen_by = 'admin' ──▶ PostgreSQL
│
▼ POST http://dixu_proxy:4000/internal/tunnels/{slug}/freeze
dixu_proxy (Elixir)
│ :ets.insert(:frozen_tunnels, {slug, "admin"}) ──▶ ETS (RAM)
│
▼ при каждом запросе на slug.dix.su
check ETS → frozen → return 503 HTML
Три рычага управления¶
| Рычаг | Где | Эффект | Обратимость |
|---|---|---|---|
Заморозка туннеля (tunnel_frozen_by = 'admin') |
Вкладка «Туннели» | Новые запросы к slug получают 503. Пользователь не может разморозить сам. | Мгновенно отменяется из той же вкладки. |
| Смена тарифа | Вкладка «Клиенты» | Убирает привилегии (TCP, аналитика, compression, приватный режим). Туннель HTTP продолжает работать. | Мгновенно. |
| Удаление аккаунта | Вкладка «Клиенты» | Slug обнуляется. Device_client теряет возможность подключиться. Туннель становится недоступен навсегда (slug освобождается). | Необратимо. |
Уведомления¶
При каждой административной заморозке/разморозке система автоматически отправляет email двум сторонам — администратору (подтверждение действия) и пользователю (уведомление о блокировке с предложением обратиться в поддержку).
// TunnelController.java
.then(emailService.notifyAdminTunnelFroze(callerEmail, user.getEmail(), user.getSlug(), true))
Смотрите также¶
- Безопасность и защита от абуза — rate-limiting, diversity-ban, anti-enumeration
- Правовые вопросы — основания для блокировки, сроки реагирования на жалобы