mirror of
https://github.com/telemt/telemt.git
synced 2026-10-07 01:45:58 +03:00
Docs 3.5.8 Pull-Up
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
+77
-18
@@ -22,11 +22,23 @@ WEB-listener Telemt
|
||||
`-- обычный или некорректный запрос --> настроенный decoy site
|
||||
```
|
||||
|
||||
Направляйте в Telemt весь публичный vhost. Если TLS-терминатор будет выделять только известные carrier paths, поведение обычных и аутентифицированных запросов станет наблюдаемо различным, а decoy policy Telemt будет обойдена.
|
||||
Направляйте в Telemt всю настроенную WEB-область. При пустом `base_path` по умолчанию это весь публичный vhost, а при непустом — точное поддерево с завершающим слешем. Если TLS-терминатор будет выделять внутри этой области только известные carrier endpoints, поведение обычных и аутентифицированных запросов станет наблюдаемо различным, а decoy policy Telemt будет обойдена.
|
||||
|
||||
Обозначим через `BASE` значение `/` при пустом `base_path` или `/<base_path>/` в остальных случаях. Публичные WEB-маршруты задаются относительно этого точного base:
|
||||
|
||||
| Метод | Путь | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `BASE?bridge=<capability>` | Исходный bridge document или recovery representation при наличии recovery-заголовка `Accept` и необязательного bearer. |
|
||||
| `POST`, `DELETE` | `BASEapi/v1/session` | Создание или закрытие parent session. |
|
||||
| `POST` | `BASEapi/v1/up` | Uplink HTTPS carrier. |
|
||||
| `POST` | `BASEapi/v1/down` | Downlink HTTPS carrier. |
|
||||
| `GET` | `BASEapi/v1/ws` | WebSocket Upgrade. |
|
||||
|
||||
`POST BASEapi/v1/diagnostic` — внутренний sideband-маршрут сгенерированного bridge, а не публичный client API. Сопоставление путей регистрозависимо и побайтно точно: нет aliases, вариантов с лишним или percent-encoded слешем и query parameters у carrier endpoints. Запрос неправильной формы с capability или bearer, аутентифицированным текущим процессом, получает локальный не кэшируемый `404`; несовпавший запрос без подлинного carrier material следует в настроенный decoy. `base_path` меняет только эти маршруты WEB-listener. Он не добавляется к Control API, `/web-status` или Prometheus metrics.
|
||||
|
||||
## Поддерживаемый контракт клиента
|
||||
|
||||
- Публичный endpoint всегда имеет вид `https://HOST:443`.
|
||||
- При пустом `base_path` публичный endpoint имеет вид `https://HOST:443/`, иначе — `https://HOST:443/BASE/`. Base path регистрозависим и проверяется точно; Telemt не перенаправляет, не нормализует и не удаляет его перед отправкой в decoy.
|
||||
- Поддерживаются 16-байтовые MTProxy-секреты `plain` и `dd`. FakeTLS-секреты `ee` в WEB-режиме не поддерживаются.
|
||||
- `web.carrier` выбирает единственный carrier при выключенном auto-negotiation и последний fallback при включённом. `https` использует сериализованные HTTPS uplink и long polling. `https-lanes` использует независимые HTTPS sequencing и polling для каждого logical stream. `websocket` использует один упорядоченный WebSocket для всех streams. `websocket-lanes` использует отдельный WebSocket с независимым ownership для каждого ненулевого logical stream.
|
||||
- Отсутствующий `web.carriers` или `web.carriers = false` отключает auto-negotiation и обучение. Непустой массив включает только стартовый последовательный перебор; уже committed session никогда не мигрирует.
|
||||
@@ -40,9 +52,10 @@ WEB-listener Telemt
|
||||
```text
|
||||
tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef
|
||||
tg://webproxy?server=proxy.example.com&secret=dd0123456789abcdef0123456789abcdef
|
||||
tg://webproxy?server=proxy.example.com%2Ftelegram%2Fweb&secret=cAABAgMEBQYHCAkKCwwNDg8
|
||||
```
|
||||
|
||||
Telemt печатает ссылки для WEB-профилей, выбранных в `[general.links].show`, через существующий log target `telemt::links`.
|
||||
При запуске процесса Telemt печатает ссылки для WEB-профилей, выбранных в `[general.links].show`, через существующий log target `telemt::links`. Root-ссылки сохраняют прежний шестнадцатеричный secret. В path-ссылке `HOST/BASE` в параметре `server` percent-encoded, а secret равен base64url без padding от `0x70 || client_secret`, где `client_secret` — исходный 16-байтовый secret для режима `plain` либо `0xdd || secret` для режима `dd`. Users API возвращает только исходный секрет, а не WEB-ссылку. `[general.links].public_host` и `public_port` влияют только на нативные ссылки и не переопределяют ссылки WEB-vhost.
|
||||
|
||||
## Предварительные требования
|
||||
|
||||
@@ -76,9 +89,12 @@ web_trusted_proxy_cidrs = ["127.0.0.1/32"]
|
||||
[web]
|
||||
enabled = true
|
||||
carrier = "https-lanes"
|
||||
decoy_fasttrack_mode = "off"
|
||||
http_connection_capacity_action = "drop"
|
||||
|
||||
[[web.vhosts]]
|
||||
host = "proxy.example.com"
|
||||
base_path = "telegram/web"
|
||||
public_addr = "203.0.113.10:443"
|
||||
|
||||
[web.vhosts.decoy]
|
||||
@@ -93,6 +109,12 @@ max_streams = 512
|
||||
max_streams_per_session = 64
|
||||
```
|
||||
|
||||
Обработка перегрузки уже принятых sockets настраивается отдельно. `drop` сохраняет прежнее закрытие после `accept(2)`. `respond` без разбора запроса записывает пустой retryable-ответ `503`. `wait` вне accept loop ожидает обычную connection capacity, а затем переходит к стандартной HTTP-обработке; timeout записывает тот же `503`. Ожидание и запись используют `web.timeouts.http_overload_timeout_ms` для каждой фазы. `web.limits.max_http_overload_connections` ограничивает sockets вне обычной capacity и требует перезапуска процесса при изменении; action и timeout поддерживают hot reload.
|
||||
|
||||
`base_path` по умолчанию пуст. Непустое значение содержит не более 128 ASCII-байт и состоит из разделённых слешами сегментов `[A-Za-z0-9][A-Za-z0-9_-]*` без начального и завершающего слеша. Root-vhost сохраняет derivation capability v1. Path-vhost использует контекст v2 с точными каноническими host и base path, поэтому изменение регистра или любого сегмента меняет и маршрут, и capability.
|
||||
|
||||
`decoy_fasttrack_mode` управляет только capability processing для `GET/HEAD` на настроенном base root. Значение `off` по умолчанию сохраняет полный прежний scan без fast-track counters. `shadow` учитывает, какие структурно невозможные запросы могли бы обойти scan, но всё равно выполняет его полностью. `enforce` обходит capability work только для `HEAD` либо отсутствующего или неканонического query `bridge`. Каждый точный канонический bridge GET на base root выполняет полный scan всех профилей выбранного vhost и при совпадении, и при промахе. Настройка требует перезапуска процесса: reload сохраняет желаемое значение, но сообщает `web.decoy_fasttrack_mode` как deferred. Fast-track не защищает от злонамеренной CPU-нагрузки, потому что scanner всегда может отправлять канонические candidates; кроме того, `enforce` может создать публично наблюдаемый timing class формы запроса, особенно при статическом decoy. Не включайте этот режим без внешних timing measurements через production TLS-терминатор.
|
||||
|
||||
## Server-side negotiation carrier
|
||||
|
||||
Auto-negotiation необязателен и выключен, пока `carriers` не задан явным непустым массивом. Настроенный `carrier` остаётся последним fallback и добавляется ровно один раз, даже если уже присутствует в массиве:
|
||||
@@ -111,20 +133,29 @@ carrier_health_secs = 30
|
||||
carrier_learning_secs = 600
|
||||
bridge_request_secs = 10
|
||||
bridge_retry_secs = 90
|
||||
bridge_recovery_secs = 15
|
||||
carrier_probe_coalesce_ms = 0
|
||||
```
|
||||
|
||||
Сгенерированный bridge отправляет канонические headers `X-Carrier-Capabilities`, `X-Carrier-Attempt` и, после первой попытки, `X-Carrier-Failure` в запросе `/session`. Каждый успешный automatic response возвращает `X-Carrier-Mode`, `X-Carrier-Attempt`, `X-Carrier-Candidate-Count`, `X-Carrier-Deadline` и `X-Carrier-State`. Bridge запускает локальный cumulative clock непосредственно перед первым запросом `/session`, а сервер фиксирует отдельный absolute chain deadline при приёме первой automatic attempt. Оба используют настроенные offsets и не сбрасываются при replacement. Для одного, двух, трёх и четырёх effective candidates checkpoints attempts равны соответственно `[d3]`, `[d0, d3]`, `[d0, d1, d3]` и `[d0, d1, d2, d3]`; финальному candidate всегда принадлежит `d3`. Successor остаётся допустимым до собственного checkpoint. Состояния: `provisional`, `committed` и `healthy`.
|
||||
|
||||
Попытки строго последовательны. Принятый прогресс `OPEN` или `DATA` немедленно фиксирует выбранный carrier и окончательно закрывает границу replacement. Аутентифицированный `409` для committed chain повторяет metadata зафиксированного carrier и является terminal response, а не разрешением перейти дальше. Точный replay `/session` применяется только пока результат этого запроса неоднозначен. После аутентифицированного выбора provisional carrier transport failure сразу запрашивает следующую attempt; если предыдущий probe всё же успел committed, сервер возвращает terminal `409` и не разрешает небезопасный replacement. Финальный абсолютный deadline на сервере также ограничивает lifetime successor, ответ которого клиент не получил. Динамическое post-commit переключение намеренно не поддерживается: для смены carrier требуется новая сессия.
|
||||
Bridge отправляет additive status objects v1 с полями `state`, `phase`, `reason` и `deadline_ms`. `phase=provisional` следует после аутентифицированного `WELCOME`; `state=connected,phase=committed` отправляется только после того, как выбранный transport подтвердит реальный прогресс `OPEN` или `DATA`. Initialization port имеет собственный pre-`HELLO` deadline `bridge_request_secs`, а навигация страницы терминальна для этого экземпляра документа. Более позднее initialization message не может оживить закрытый или оставшийся в BFCache bridge.
|
||||
|
||||
Каждая HTTP-операция bridge имеет абсолютный budget `bridge_retry_secs` и не более девяти attempts. `bridge_request_secs` охватывает Fetch response head и полное чтение response body; для downlink attempt дополнительно разрешён настроенный long-poll interval. Network failures и ответы `408`, `429`, `502`, `503` или `504` используют bounded exponential backoff, а `Retry-After` не может расширить абсолютный budget. При `carrier_probe_coalesce_ms = 0` первый упорядоченный probe с `OPEN` отправляется немедленно. Значение до 10 мс позволяет включить соответствующий `DATA`, пришедший в этом окне; multiplexed carriers сохраняют весь предшествующий порядок frames, а lane carriers забирают только выбранную lane. HTTP downlink не запускается до acknowledgement probe. Multiplexed WebSocket Upgrade может начаться сразу после его выбора ответом `/session` и затем включить queued probe data; lane WebSocket ждёт известного stream ID.
|
||||
Попытки строго последовательны. Принятый прогресс `OPEN` или `DATA` немедленно фиксирует выбранный carrier и окончательно закрывает границу replacement. Аутентифицированный `409` для committed chain повторяет metadata зафиксированного carrier и является terminal response, а не разрешением перейти дальше. Точный replay `/session` применяется только пока результат этого запроса неоднозначен. После аутентифицированного выбора provisional carrier transport failure сразу запрашивает следующую attempt; если предыдущий probe всё же успел committed, сервер возвращает terminal `409` и не разрешает небезопасный replacement. Финальный абсолютный deadline на сервере также ограничивает lifetime successor, ответ которого клиент не получил. In-place переключение после commit по-прежнему не поддерживается; сохранившийся bridge восстанавливается созданием новой server session.
|
||||
|
||||
После commit при HTTP failure сначала точно повторяется замороженный request с текущим bearer. Успешный replay сохраняет текущую session. Потеря WebSocket либо событие foreground, online или native после scheduler gap не короче `reconnect_grace_secs` запускает одну recovery epoch. Bridge выполняет ровно один GET к исходному настроенному base root с `bridge=<capability>`, `Accept: application/vnd.telemt.web-recovery+json` и необязательной authorization текущим bearer. Положительный ответ — не кэшируемый JSON document размером не более 1024 байт со свежим bootstrap и текущими limits, timeouts и negotiation policy. Telemt выдаёт этот bootstrap до синхронного завершения совпавшей текущей session, поэтому пересоздание остаётся возможным при capacity в одну session. Неизвестный или уже retired bearer получает то же положительное representation; malformed recovery headers, выключенная admission, pause, drain и capacity rejection следуют по очищенному decoy path.
|
||||
|
||||
Recovery epoch имеет единый абсолютный wall/monotonic deadline `bridge_recovery_secs`, один request recovery document и bounded carrier retries с backoff от 250 мс до 2 с. Активный recovery status повторяется не чаще одного раза в 2,5 секунды. Новая incarnation прерывает и освобождает старые requests, sockets, lanes и queues, отправляет один synthetic `CLOSE` для каждого ещё активного native stream, подавляет второй `WELCOME` и выполняет commit только после реального carrier progress. Retired stream IDs сохраняются в bounded set, чтобы корректные поздние frames не попали в новый stream; native side должна выделить новый stream ID. Частые native reconnect attempts допустимы, но не продлевают recovery epoch и не удерживают старое состояние incarnation. Уничтожение WebView уничтожает этого recovery owner; после этого native supervisor должен создать новый bridge document.
|
||||
|
||||
Каждая обычная carrier HTTP-операция bridge имеет абсолютный budget `bridge_retry_secs` и не более девяти attempts. `bridge_request_secs` охватывает Fetch response head и полное чтение response body; для downlink attempt дополнительно разрешён настроенный long-poll interval. Network failures и ответы `408`, `429`, `502`, `503` или `504` используют bounded exponential backoff, а `Retry-After` не может расширить абсолютный budget. При `carrier_probe_coalesce_ms = 0` первый упорядоченный probe с `OPEN` отправляется немедленно. Значение до 10 мс позволяет включить соответствующий `DATA`, пришедший в этом окне; multiplexed carriers сохраняют весь предшествующий порядок frames, а lane carriers забирают только выбранную lane. HTTP downlink не запускается до acknowledgement probe. Multiplexed WebSocket Upgrade может начаться сразу после его выбора ответом `/session` и затем включить queued probe data; lane WebSocket ждёт известного stream ID.
|
||||
|
||||
Response bodies читаются потоково с явными bounds endpoint: `/session` содержит ровно восемь байт, успешный `/down` — не более `carrier_batch_bytes`, а bodyless responses допускают ноль байт. Заявленный overflow отклоняется до чтения; overflow при streaming или избыточное число chunks отменяет reader, а bodies retryable responses отменяются до backoff. Финальный cleanup bridge отправляет не более одного аутентифицированного `DELETE`; канонические transport failures копируются в `X-Carrier-Failure` для диагностики, а navigation и explicit close остаются non-learning reasons.
|
||||
|
||||
Automatic WebSocket использует `tproxy-auto-v1.<session-token>` или `tproxy-auto-lane-v1.<session-token>.<stream-id>`. Первое принятое binary message с реальным прогрессом `OPEN` или `DATA` фиксирует carrier; затем сервер пишет пустой binary commit ACK именно в это connection. Ping/Pong не фиксирует carrier и не считается learning evidence.
|
||||
|
||||
Committed attempt становится healthy, только когда transport-specific двунаправленный evidence остаётся корректным в течение `carrier_health_secs`. HTTPS требует принятый `DATA`, подтверждённый непустой post-commit downlink batch и аутентифицированную активность не раньше health deadline. WebSocket требует записи точного commit ACK, последующего принятого `OPEN` или `DATA` от того же owner и сохранения этого owner живым до конца интервала. Более раннее закрытие нейтрально и не записывает результат обучения.
|
||||
Committed attempt становится healthy, только когда transport-specific двунаправленный evidence остаётся корректным в течение `carrier_health_secs`. HTTPS требует принятый `DATA`, подтверждённый непустой post-commit downlink batch и аутентифицированную активность не раньше health deadline. WebSocket требует записи точного commit ACK, последующего принятого `OPEN` или `DATA` от того же owner и сохранения этого owner живым до конца интервала. Health publication, owner eviction и close имеют единственного terminal winner. Более раннее закрытие нейтрально для ranking evidence, но отображается как diagnostic outcome `closed_before_health`.
|
||||
|
||||
Обучение process-local, in-memory, positive-only и ограничено `max_carrier_learning_entries`. Оно ранжирует только поддерживаемые клиентом настроенные candidates, всегда оставляет fallback последним и сохраняет настроенный порядок при равных scores. Evidence User-Agent и профиля имеет основной вес; допустимый IP служит только tie-breaker. Для IP evidence требуется ровно один явный глобально маршрутизируемый `X-Forwarded-For`; private, loopback, link-local, carrier-grade NAT, documentation, multicast и их IPv4-mapped эквиваленты исключаются. Категории ошибок от клиента и request latency используются только для диагностики и не создают отрицательный или ranking evidence. `conservative` требует 3 outcomes User-Agent или 8 outcomes профиля в 4 cohorts и отключает IP evidence; `balanced` использует соответственно 2, 6 в 3 cohorts и 3 outcomes допустимого IP; `aggressive` — 1, 4 в 2 cohorts и 1 outcome IP. Выключение обучения или смена policy при reload очищает несовместимый evidence, не меняя уже начатые сессии.
|
||||
Обучение process-local, in-memory, positive-only и ограничено `max_carrier_learning_entries`. Оно ранжирует только поддерживаемые клиентом настроенные candidates, всегда оставляет fallback последним и сохраняет настроенный порядок при равных scores. Evidence User-Agent и профиля имеет основной вес; допустимый IP служит только tie-breaker. Для IP evidence требуется ровно один явный глобально маршрутизируемый `X-Forwarded-For`; private, loopback, link-local, carrier-grade NAT, documentation, multicast и их IPv4-mapped эквиваленты исключаются. Категории ошибок от клиента и request latency используются только для диагностики и не создают отрицательный или ranking evidence. `conservative` требует 3 outcomes User-Agent или 8 outcomes профиля в 4 cohorts и отключает IP evidence; `balanced` использует соответственно 2, 6 в 3 cohorts и 3 outcomes допустимого IP; `aggressive` — 1, 4 в 2 cohorts и 1 outcome IP. Смена generation при неизменной learning semantics сохраняет evidence и атомарно перепубликует его generation fence. Выключение обучения или изменение aggressiveness, lifetime evidence либо health window увеличивает evidence epoch и отсоединяет несовместимое состояние; stale outcomes не могут заполнить его снова.
|
||||
|
||||
`https` остаётся default и сохраняет исходное сериализованное поведение. В `https-lanes` lane zero отведена под session control, а каждому ненулевому logical stream соответствует своя lane. У каждой lane собственные uplink sequence, retry digest, downlink cursor, unacknowledged replay batch, очередь и lifecycle newest-poll-wins. Поэтому медленный stream не блокирует другой stream на уровне WEB-протокола.
|
||||
|
||||
@@ -132,9 +163,9 @@ Committed attempt становится healthy, только когда transpor
|
||||
|
||||
Все lane queues и resident response bodies входят в существующие per-session и process-wide byte/item budgets. Telemt дополнительно ограничивает одну lane значениями `pending_bytes_per_lane` и `pending_items_per_lane`; сгенерированный bridge ограничивает свои очереди 8 MiB и 1024 элементами. Lane long polls могут занимать не более половины `web.limits.max_http_handlers`, оставляя handler capacity для session creation, uplink, DELETE и другой control work. Для `https` требуется `max_http_handlers >= 2`, для `https-lanes` — `max_http_handlers >= 4`.
|
||||
|
||||
Paths `/api/v1/up` и `/api/v1/down` не меняются. В `https-lanes` каждый запрос к ним содержит один канонический десятичный `X-Lane-ID`. Uplink sequence начинается с `1`, а downlink cursor — с `0` независимо для каждой lane. Lane zero принимает только session `PONG`; все frames ненулевой lane должны иметь тот же stream ID, а новая lane должна начинаться с `OPEN`. Канонический downlink с cursor zero, пришедший немного раньше `OPEN` своей lane, ждёт до `lane_open_wait_secs` без создания lane state; число таких ожиданий ограничено per-session и process auxiliary permits. Истечение таймаута возвращает пустой `204`, а отсутствующая lane с продвинутым cursor остаётся protocol failure и уходит в decoy. После отправки всей queued и unacknowledged downlink data закрытой lane Telemt возвращает пустой ответ с `X-Lane-Closed: 1`, и bridge прекращает её polling. Retry остаются byte-identical и повторяют исходный acknowledgement или downlink batch.
|
||||
Суффиксы `/api/v1/up` и `/api/v1/down` не меняются и добавляются к настроенному base. В `https-lanes` каждый запрос к ним содержит один канонический десятичный `X-Lane-ID`. Uplink sequence начинается с `1`, а downlink cursor — с `0` независимо для каждой lane. Lane zero принимает только session `PONG`; все frames ненулевой lane должны иметь тот же stream ID, а новая lane должна начинаться с `OPEN`. Канонический downlink с cursor zero, пришедший немного раньше `OPEN` своей lane, ждёт до `lane_open_wait_secs` без создания lane state; число таких ожиданий ограничено per-session и process auxiliary permits. Истечение таймаута возвращает пустой `204`, а отсутствующая lane с продвинутым cursor остаётся protocol failure и уходит в decoy. После отправки всей queued и unacknowledged downlink data закрытой lane Telemt возвращает пустой ответ с `X-Lane-Closed: 1`, и bridge прекращает её polling. Retry остаются byte-identical и повторяют исходный acknowledgement или downlink batch.
|
||||
|
||||
Оба WebSocket carrier по-прежнему создают и удаляют parent session через HTTPS, после чего используют строгий bodyless Upgrade-запрос `GET /api/v1/ws`. `websocket` передаёт в `Sec-WebSocket-Protocol` ровно `tproxy-v1.<session-token>`; binary messages являются упорядоченными carrier batches, а ошибка протокола, deadline или connection закрывает всю parent session. `websocket-lanes` передаёт ровно `tproxy-lane-v1.<session-token>.<stream-id>`, где stream ID записан каноническим десятичным числом из диапазона `1..=16777215`. Первое binary message должно начинаться с `OPEN`, все frames должны содержать этот stream ID, а сбой после Upgrade закрывает только данную lane. Lane-zero WebSocket отсутствует: HTTPS переносит `HELLO` и `WELCOME`, а liveness connection обеспечивает RFC 6455 Ping/Pong.
|
||||
Оба WebSocket carrier по-прежнему создают и удаляют parent session через HTTPS, после чего используют строгий bodyless Upgrade GET по настроенному base плюс `/api/v1/ws`. `websocket` передаёт в `Sec-WebSocket-Protocol` ровно `tproxy-v1.<session-token>`; binary messages являются упорядоченными carrier batches, а ошибка протокола, deadline или connection закрывает всю parent session. `websocket-lanes` передаёт ровно `tproxy-lane-v1.<session-token>.<stream-id>`, где stream ID записан каноническим десятичным числом из диапазона `1..=16777215`. Первое binary message должно начинаться с `OPEN`, все frames должны содержать этот stream ID, а сбой после Upgrade закрывает только данную lane. Lane-zero WebSocket отсутствует: HTTPS переносит `HELLO` и `WELCOME`, а liveness connection обеспечивает RFC 6455 Ping/Pong.
|
||||
|
||||
До HTTP `101` reservation WebSocket lane привязывается к точным process connection и incarnation lane; принятый `OPEN` передаёт ownership точному incarnation stream до запуска его backend task. Поздний poll, close или drop reservation от старого socket не может подтвердить, закрыть или освободить replacement, повторно использующий тот же числовой lane ID.
|
||||
|
||||
@@ -144,7 +175,7 @@ WebSocket codec buffers и находящиеся в обработке read/wri
|
||||
|
||||
Для WEB-listener обязательны `proxy_protocol = false` и `reuse_allow = false`. В нём нельзя использовать `client_mss`, `synlimit`, `announce` и `announce_ip`. Массив `web_trusted_proxy_cidrs` должен быть непустым и содержать только непосредственные адреса NGINX или HAProxy; сети `/0` запрещены.
|
||||
|
||||
HTTP decoy origin должен быть loopback, link-local или private IP literal. Для обычных запросов Telemt сохраняет method, path, query, headers, streamed body, response status, headers и body, удаляя hop-by-hop headers. Перед отправкой некорректного carrier-запроса в decoy Telemt удаляет из него carrier credentials и body.
|
||||
HTTP decoy origin должен быть loopback, link-local или private IP literal. Для обычных запросов Telemt сохраняет method, path, query, headers, streamed body, response status, headers и body, удаляя hop-by-hop headers. Перед отправкой некорректного carrier-запроса в decoy Telemt удаляет из него carrier credentials и body. Literal decoy endpoint отклоняется, если он точно совпадает с effective WEB-listener либо покрывается wildcard address того же IP-семейства на том же порту. Косвенные loops через DNS, NGINX, HAProxy или другой forwarding layer нельзя доказать из конфигурации Telemt; оператор обязан исключить их самостоятельно.
|
||||
|
||||
Вместо origin можно использовать immutable snapshot статического сайта:
|
||||
|
||||
@@ -203,8 +234,18 @@ server {
|
||||
|
||||
Разместите `map` в контексте `http` NGINX. `client_max_body_size` должен быть не меньше `web.limits.max_body_bytes`. Read, send и client timeouts должны превышать как default long poll в 25 секунд, так и удвоенный WebSocket liveness interval; 65 секунд покрывают defaults. Перезаписывайте `X-Forwarded-For`, а не дополняйте его. Telemt принимает один корректно разбираемый IP-адрес; если доверенный TLS-терминатор не передал header, Telemt использует адрес непосредственного peer, но per-client limits и source policy тогда видят терминатор вместо реального клиента. Не включайте upstream retries: bridge выполняет byte-identical HTTPS retries, но установленный WebSocket никогда не replay’ится прозрачно.
|
||||
|
||||
Для prefix-only cohosting с `base_path = "telegram/web"` замените `location /` на `location ^~ /telegram/web/`. Оставьте `proxy_pass http://telemt_web;` без URI-компонента и не добавляйте `rewrite`: NGINX должен передавать исходный prefix. Запросы вне этого поддерева может обслуживать другой сайт, но все запросы внутри него должны идти в Telemt. Также задайте точный `location = /telegram/web`, который использует обычное non-WEB-поведение сайта или без изменений передаёт запрос в decoy path Telemt. Иначе NGINX может самостоятельно создать добавляющий слеш `301` для alias без слеша, который не входит в WEB-контракт.
|
||||
|
||||
Для `https-lanes` обязателен публичный HTTP/2; используйте эквивалентную HTTP/2-директиву, поддерживаемую установленной версией NGINX. WebSocket Upgrade требует HTTP/1.1, поэтому публичный endpoint должен также разрешать HTTP/1.1, а приватный hop NGINX-to-Telemt остаётся HTTP/1.1. Сохраняйте `Connection`, `Upgrade` и `Sec-WebSocket-*` ровно как в примере. Upstream connection capacity должна выдерживать ожидаемое число одновременных lane polls или WebSocket lanes; `keepalive` управляет idle pool и не является лимитом concurrency.
|
||||
|
||||
### Как отличить отказ соединения от WEB capacity
|
||||
|
||||
`connect() failed (111: Connection refused) while connecting to upstream` означает ошибку TCP connect до принятия socket процессом Telemt. Проверьте, что процесс Telemt запущен, effective address и port WEB-listener совпадают с upstream NGINX, оба процесса находятся в ожидаемых network namespace и address family, а локальный firewall не отклоняет соединение. Такое поведение могут вызвать ошибка bind при запуске, окончательное удаление listener или переключение NGINX на желаемый порт до того, как restart-only изменение listener стало эффективным. Давление на kernel listen backlog — отдельный случай, для которого обычно нужна host telemetry `ListenOverflows` и `ListenDrops`.
|
||||
|
||||
WEB capacity применяется после успешного `accept(2)`. Поэтому исчерпание `max_http_connections` приводит к настроенному outcome `drop`, `wait` или `respond`, но не к отказу upstream connect. У limits handler, body, lane, stream, queue и WebSocket собственные HTTP-, decoy- или stream-local failure boundaries. Operator pause и drain также оставляют WEB-listener привязанным и сами по себе не могут вызвать отказ соединения.
|
||||
|
||||
Используйте `GET /v1/runtime/web/status` только для корреляции состояния, принадлежащего Telemt. Для `ingress.accepting_connections` нужны running publication, доступный runtime и один живой acceptor на каждый effective WEB-listener. `capacity.saturated_resources`, типизированные rejection totals и overload outcomes показывают сбои после accept. `decoy_upstream` описывает только исходящий plain-HTTP hop Telemt к decoy. Ни одно из этих полей не утверждает, что публичный TLS endpoint NGINX доступен; проверяйте эту границу внешним TCP/TLS probe и telemetry NGINX или HAProxy.
|
||||
|
||||
## Терминация TLS на HAProxy
|
||||
|
||||
```haproxy
|
||||
@@ -227,7 +268,7 @@ backend telemt_web
|
||||
server telemt_web_1 127.0.0.1:18080 check
|
||||
```
|
||||
|
||||
Во frontend или секции `defaults` также задайте `timeout client 65s` или больше для default WebSocket liveness interval. Для `https-lanes` публичный ALPN HAProxy должен содержать `h2`, а для WebSocket Upgrade — `http/1.1`. Сохраняйте `Connection`, `Upgrade` и `Sec-WebSocket-*`; не переписывайте path, raw query, body и carrier headers `Authorization`, `Content-Type`, `X-Up-Seq`, `X-Down-Cursor`, `X-Lane-ID`.
|
||||
Во frontend или секции `defaults` также задайте `timeout client 65s` или больше для default WebSocket liveness interval. Для `https-lanes` публичный ALPN HAProxy должен содержать `h2`, а для WebSocket Upgrade — `http/1.1`. Сохраняйте `Connection`, `Upgrade` и `Sec-WebSocket-*`; не переписывайте path, raw query, body и carrier headers `Authorization`, `Content-Type`, `X-Up-Seq`, `X-Down-Cursor`, `X-Lane-ID`. Для prefix-only cohosting добавьте `acl telemt_web_path path_beg /telegram/web/` и потребуйте одновременно host- и path-ACL в `use_backend`; не удаляйте prefix.
|
||||
|
||||
## Lifecycle и reload
|
||||
|
||||
@@ -236,7 +277,9 @@ backend telemt_web
|
||||
| Состав WEB-listeners, bind address и trust policy | Принадлежат процессу; перезапустите Telemt. |
|
||||
| Любое значение `[web.limits]` | Process-owned контракт памяти и ресурсов; перезапустите Telemt. |
|
||||
| `web.enabled`, policy carrier/negotiation, `web.debug`, timeouts, vhosts, profiles и decoys | Применяются config watcher или runtime generation reload. |
|
||||
| Существующие HTTP connections и WEB sessions | Сохраняют HTTP idle limit, carrier candidates, лимиты, body timeout, lifetime replay-marker закрытого token и абсолютные session/negotiation deadlines своего момента создания; каждый выданный bridge содержит собственные request, retry и probe-coalescing значения. WebSocket Upgrade, open, write, backpressure и eviction operations используют замороженные deadlines parent session. Новые bridges получают активную policy, а новые logical streams используют активное relay generation. |
|
||||
| Изменение `base_path` vhost | Атомарно переключает маршрутизацию новых HTTP-запросов и derivation capability. Выпустите новую сгенерированную ссылку. Уже upgraded WebSockets и начатые маршрутизированные обмены продолжаются. Последующие запросы к старому base с подлинным для процесса bootstrap- или session-token получают локальный no-store `404`, а ставшая неактивной прежняя capability обрабатывается как обычный decoy traffic. Существующий session bearer остаётся пригодным только на новом точном base, а неиспользованный bootstrap, выданный для старой capability, не может создать session на новом base. |
|
||||
| Operator pause/drain state | Process-owned и ephemeral; переживает generation reload, никогда не записывает конфигурацию и после перезапуска процесса возвращается в `running`. |
|
||||
| Существующие HTTP connections и WEB sessions | Сохраняют HTTP idle limit, carrier candidates, лимиты, body timeout, lifetime replay-marker закрытого token и абсолютные session/negotiation deadlines своего момента создания; каждый выданный bridge содержит собственные request, retry, recovery и probe-coalescing значения. Recovery epoch фиксирует текущий bridge budget, а успешное recovery representation обновляет policy для последующих epochs и новой session. WebSocket Upgrade, open, write, backpressure и eviction operations используют замороженные deadlines parent session. Новые bridges получают активную policy, а новые logical streams используют активное relay generation. |
|
||||
| Завершение процесса | Один раз фиксирует последнее применённое значение `web.timeouts.shutdown_secs` и использует единый абсолютный deadline для listener acceptors и connections, WEB sessions и auxiliary tasks. Последовательные компоненты не получают отдельные полные бюджеты. |
|
||||
|
||||
Каждый logical stream сохраняет client IP своей сессии и владеет уникальным в пределах процесса ненулевым synthetic source port до завершения relay. Это сохраняет один стабильный непересекающийся source/destination tuple для Direct и Middle-End KDF routing.
|
||||
@@ -245,6 +288,8 @@ HTTP idle accounting защищает только явно ограниченн
|
||||
|
||||
`OPEN` резервирует bounded ownership logical stream и tuple, но не занимает permit `max_connections` relay generation. Telemt получает этот permit только после первого внутреннего байта; замороженный first-byte deadline и stream limits ограничивают silent opens, а исчерпание capacity закрывает только затронутый stream.
|
||||
|
||||
Рассматривайте изменение `base_path` на работающей системе как миграцию маршрута, несущего credentials. Прекратите выдавать или распространять старые ссылки, подготовьте новую ссылку, по возможности выполните drain затронутых sessions, примените reload, проверьте новый маршрут через публичный TLS endpoint и только затем распространяйте новую ссылку. Пока ещё могут приходить старые capabilities или tokens, направляйте в Telemt и старый, и новый frontend prefixes: credential-aware локальный отказ должен выполнить Telemt. Один vhost не может одновременно принимать оба base. Для реального overlap window нужен второй hostname/vhost, а если необходимо сохранить тот же host — отдельная process или deployment boundary.
|
||||
|
||||
## Управление через API
|
||||
|
||||
Конфигурация WEB, статус runtime и bounded runtime-управление доступны на одном аутентифицированном API-listener. `/web-status` остаётся read-only HTML-диагностикой; операции, изменяющие состояние, существуют только под `/v1/runtime/web`.
|
||||
@@ -257,6 +302,7 @@ HTTP idle accounting защищает только явно ограниченн
|
||||
| Просмотр bounded серверных WEB request- и lifecycle-деталей | Да, через аутентифицированный `GET /web-status`. |
|
||||
| Просмотр lifecycle, capacity planes, состояния learning/debug и активных сессий | Да, через `GET /v1/runtime/web/status` и `/v1/runtime/web/sessions`. |
|
||||
| Закрытие выбранных активных WEB-сессий | Да, через асинхронную операцию `POST /v1/runtime/web/sessions/close`. |
|
||||
| Приостановка, deadline-drain или возобновление новой WEB-работы | Да, через `/v1/runtime/web/lifecycle/{pause,drain,resume}`. |
|
||||
| Очистка debug-записей или сброс carrier learning | Да, через соответствующие runtime POST endpoints. |
|
||||
| Управление `[access.users]` | Да, через `/v1/users`. Создание пользователя не создаёт WEB-профиль. |
|
||||
| Отзыв отдельного пользователя | Да. `/v1/users/{username}/disable` немедленно обновляет admission и завершает активные сессии пользователя. |
|
||||
@@ -276,18 +322,25 @@ API whitelist проверяет непосредственный TCP peer и н
|
||||
|
||||
### Статус и управление runtime
|
||||
|
||||
`GET /v1/runtime/web/status` всегда возвращает опубликованный lifecycle (`starting`, `no_web_listener`, `running`, `draining`, `drained` или `deadline_exceeded`), его epoch и возраст, эффективные адреса listeners и доступность. Пока process-owned WEB runtime существует, поле `runtime` добавляет случайный 128-битный `runtime_instance`, активное поколение, неизменяемые limits, capacity counters отдельных planes, epochs carrier-learning/debug и суммарные counters. Status собирается неблокирующим чтением каждого plane: занятый plane пропускается и указывается в `partial`; endpoint никогда не ожидает data plane, не выполняет cleanup и не изменяет его.
|
||||
`GET /v1/runtime/web/status` всегда возвращает опубликованный ingress lifecycle (`starting`, `no_web_listener`, `running`, `draining`, `drained` или `deadline_exceeded`), его epoch и возраст, effective addresses listeners и обратно совместимую runtime availability. `ingress` независимо сообщает configured listeners, live acceptors, accepting state, accept totals и стабильную причину. `capacity` сообщает effective policy перегрузки принятых sockets, использование фиксированных ресурсов, мгновенную saturation, partial planes, типизированные rejection decisions и overload outcomes. `decoy_upstream` сообщает фиксированные outcomes и возраст последнего внутреннего результата origin. `decoy_fasttrack` сообщает effective restart-frozen mode и полный фиксированный набор dispositions, даже если runtime manager недоступен. `carrier_negotiation` всегда сообщает фиксированные matrices selection, client failure и terminal health/learning outcomes из publication ownership. Пока process-owned WEB runtime существует, `operator_lifecycle` независимо показывает `running`, `paused`, `draining`, `force_closing` или `drained`, собственные epoch и admission flags, а также активный или последний drain. Поле `runtime` добавляет случайный 128-битный `runtime_instance`, активное поколение, неизменяемые limits, capacity counters отдельных planes, epochs carrier-learning/debug и суммарные counters. Status собирается неблокирующим чтением каждого plane: занятый plane пропускается и указывается в `partial`; endpoint никогда не ожидает data plane, не выполняет cleanup и не изменяет его.
|
||||
|
||||
Prometheus экспортирует те же process-owned planes как семейства `telemt_web_*` с фиксированной cardinality: one-hot states ingress и operator, listener/accept counters, использование и saturation capacity, типизированные terminal rejections, outcomes перегрузки принятых sockets, внутренние outcomes decoy origin и totals sessions/streams/carriers. Decoy routing добавляет one-hot `telemt_web_decoy_fasttrack_mode` и фиксированный `telemt_web_decoy_fasttrack_requests_total{disposition}`. Carrier negotiation использует `telemt_web_carrier_selections_total`, `telemt_web_carrier_reported_failures_total`, `telemt_web_carrier_learning_outcomes_total`, one-hot gauges learning state/policy и gauges used/limit entries. Labels — только закрытые enums или фиксированные имена ресурсов; user, host, client IP, token, profile key, runtime instance, listener address и generation ID никогда не используются как labels. Успешный outcome `wait` не увеличивает rejection counter.
|
||||
|
||||
`GET /v1/runtime/web/sessions` возвращает не более 50 сессий по умолчанию и не более 200 при заданном `limit`. Упорядоченный scan ограничен 1000 кандидатами. `cursor` и `session_ref` имеют opaque canonical вид `ws1.<runtime-instance>.<lowercase-hex-id>`; точный `session_ref` нельзя сочетать с `cursor` или `limit`. Доступны фильтры `ip`, `host`, `user`, `user_agent_id`, `key_id`, `carrier` и `state`; повторяющиеся или неизвестные query fields отклоняются. Детальная операция — `GET /v1/runtime/web/sessions/{session_ref}`. Сохранённый tombstone закрытой сессии возвращает `410`; занятый точный snapshot — `503 web_snapshot_busy`. Ответы содержат только bounded несекретные metadata и никогда не раскрывают bootstrap/session bearers, capabilities, hashes секретов или synthetic/KDF ports.
|
||||
|
||||
Каждый runtime POST требует ровно `Content-Type: application/json`, отклоняет неизвестные JSON fields, наследует API authentication, whitelist и `read_only`, а также содержит текущий `runtime_instance` как ABA-fence. Доступные операции:
|
||||
|
||||
- `POST /v1/runtime/web/lifecycle/pause` с `{"runtime_instance":"..."}`. После linearizable fence операция блокирует новые bootstrap, session incarnation, replacement и logical-stream admission. Существующие carrier exchanges и streams продолжаются, точный session replay остаётся доступным, а отказ bridge сохраняется на decoy route.
|
||||
- `POST /v1/runtime/web/lifecycle/drain` с `{"runtime_instance":"...","timeout_secs":30}`. Операция возвращает `202`, сохраняет ту же admission fence закрытой и асинхронно ожидает sessions, streams и session-owned WebSockets. На monotonic deadline она сигнализирует close всем оставшимся live sessions и сообщает `force_closing`, пока не подтверждён ноль. И естественное, и принудительное завершение остаются закрытыми до resume. Одновременный второй drain возвращает `409 web_lifecycle_in_progress`.
|
||||
- `POST /v1/runtime/web/lifecycle/resume` с `{"runtime_instance":"..."}`. Операция отменяет активный drain и повторно открывает только operator admission. Если forced close уже committed, прежнюю cancellation sessions нельзя отменить. Gates config, user, generation и terminal shutdown по-прежнему имеют приоритет.
|
||||
- `POST /v1/runtime/web/sessions/close` с одним selector: `{"kind":"refs","session_refs":[...]}`, `{"kind":"filter",...}` или `{"kind":"all"}`. Точные refs ограничены 200, filter должен быть непустым, одновременно выполняется не более одной close operation, а `all` отклоняется, пока effective issuance включён. Ответ `202` содержит `operation_id`; опрашивайте `GET /v1/runtime/web/operations/{operation_id}`. Операция chunks по 128 сканирует только сессии не выше submission high-water mark.
|
||||
- `POST /v1/runtime/web/debug/clear` с `{"runtime_instance":"..."}`. Ответ содержит число удалённых записей, bytes, всё ещё удерживаемые уже отрисовываемыми snapshots, и новый epoch. In-flight writers старого epoch не могут снова заполнить ring.
|
||||
- `POST /v1/runtime/web/carrier-learning/reset` с тем же body. Операция очищает сохранённый process-local evidence и увеличивает learning epoch; уже замороженные attempt chains и активные сессии не изменяются.
|
||||
|
||||
Для детерминированного close-all отправьте patch `{"web":{"enabled":false}}` с включённым runtime reload, дождитесь `runtime.manager.issuance_enabled = false`, отправьте selector `all` с тем же `runtime_instance` и опрашивайте operation до terminal state. Отключение WEB прекращает новую выдачу bootstrap/session credentials, но никогда не закрывает существующие сессии неявно.
|
||||
|
||||
Operator lifecycle относится только к WEB и не меняет глобальные readiness/liveness, нативные TCP/Unix listeners, TLS-fronting или fallback behavior. Reservation WebSocket lane, сделанный до pause, уже считается допущенной logical work: он может завершить open и продолжает учитываться в drain. Lifecycle rejection не расходует rate/quota tokens и не добавляет relay lock в hot path.
|
||||
|
||||
### Серверная WEB-отладка
|
||||
|
||||
Включите bounded сбор в конфигурационном файле, которому принадлежит эта секция:
|
||||
@@ -296,6 +349,7 @@ API whitelist проверяет непосредственный TCP peer и н
|
||||
[web.debug]
|
||||
enabled = true
|
||||
capture_lifecycle = true
|
||||
sideband = true
|
||||
capture_headers = true
|
||||
capture_timings = true
|
||||
capture_frames = true
|
||||
@@ -306,12 +360,14 @@ default_window_secs = 180
|
||||
max_window_secs = 3600
|
||||
```
|
||||
|
||||
Откройте `http://127.0.0.1:9091/web-status`, используя те же whitelist непосредственных peers и точный header `Authorization`, что и для API. Завершающий slash разрешён. Допускается только `GET`. Страница поддерживает фильтры `window_secs`, канонический `ip`, числовой `session`, регистронезависимый `user_agent` и `key`. Повторяйте `group_by=ip`, `group_by=session`, `group_by=user_agent` или `group_by=key` для построения сгруппированных сводок; `limit` ограничен диапазоном `1..=1000`. HTTP rows раскрываются от request до response с method, path, очищенными headers, метаданными или байтами body, timing points, frames и типизированными lifecycle events, включая carrier attempt, commit, healthy и reported-failure transitions. Для WebSocket добавляются очищенный handshake `GET` → `101` и bounded per-message direction, message type, payload/body capture, processing time, connection/lane identifiers и разобранные inner frames. Raw subprotocol и session tokens никогда не сохраняются.
|
||||
Откройте `http://127.0.0.1:9091/web-status`, используя те же whitelist непосредственных peers и точный header `Authorization`, что и для API. Завершающий slash разрешён. Допускается только `GET`. Страница поддерживает фильтры `window_secs`, канонический `ip`, числовой `session`, регистронезависимый `user_agent` и `key`. Повторяйте `group_by=ip`, `group_by=session`, `group_by=user_agent` или `group_by=key` для построения сгруппированных сводок; `limit` ограничен диапазоном `1..=1000`. HTTP rows раскрываются от request до response с method, path, очищенными headers, метаданными или байтами body, timing points, frames и типизированными lifecycle events, включая carrier attempt, commit, healthy, reported failure, точную причину close, peer gap и переходы predecessor восстановленной session. Для WebSocket добавляются очищенный handshake `GET` → `101` и bounded per-message direction, message type, payload/body capture, processing time, connection/lane identifiers и разобранные inner frames. Raw subprotocol и session tokens никогда не сохраняются.
|
||||
|
||||
Process-owned кольцевой буфер переживает замену runtime generation. Изменения capture policy очищают несовместимые сохранённые записи; изменения только окна наблюдения этого не делают. По умолчанию кольцо ограничено 65536 записями и 64 MiB сохранённых плюс находящихся в обработке данных, HTML-response — 8 MiB, grouping — 1024 группами; одновременно page permits могут удерживать не более двух response bodies. Изменяйте `web.limits.debug_records_capacity` или `web.limits.debug_bytes_global` только с перезапуском процесса. Hot prefix, который помещается только в одновременно увеличенную restart-only ёмкость, откладывается до этого перезапуска.
|
||||
|
||||
`body_capture = "off"` исключает bodies, `metadata` сохраняет длину и terminal state, `prefix` — настроенные prefixes, а `full` — распознанные carrier bodies до `web.limits.max_body_bytes`. Обычные decoy bodies даже в режиме `full` ограничены `decoy_body_prefix_bytes`. Queries и raw capabilities никогда не сохраняются; значения credential headers исключаются; известные WEB capabilities и bearer tokens удаляются из захваченных bodies; отображаемый ключ является несекретным domain-separated fingerprint. Timing заканчивается на polling Hyper body и не означает kernel flush или TCP acknowledgment.
|
||||
|
||||
Sideband reporting сгенерированного bridge действует, только когда `enabled`, `capture_lifecycle` и `sideband` одновременно равны `true`. Policy поддерживает hot reload, но reporter содержат только новые выданные bridge pages. Каждая страница может не более одного раза сообщить каждое из восьми фиксированных событий: `runtime_started`, `status_posted`, `hello_received`, `boundary_timeout`, `hello_timeout`, `client_close_before_hello`, `document_unloaded_before_hello` и `runtime_error_before_hello`. Reports являются точными каноническими JSON POST к `BASEapi/v1/diagnostic`, используют bootstrap bearer, не расходуя его, и не участвуют в carrier framing. Malformed или unauthenticated reports следуют по очищенному decoy path.
|
||||
|
||||
После атомарного изменения TOML-файла администратором или системой управления конфигурацией задайте в `TELEMT_API_AUTH` точное значение `auth_header` и отправьте наблюдаемый generation reload:
|
||||
|
||||
```bash
|
||||
@@ -347,20 +403,21 @@ curl -sS -X POST http://127.0.0.1:9091/v1/users/web-user/rotate-secret \
|
||||
|
||||
- Никогда не публикуйте plain HTTP WEB-listener в недоверенной сети. Закрепите это host firewall rules, даже если listener использует loopback.
|
||||
- Отключите логирование request target и authorization на TLS-терминаторе либо используйте проверенный формат с редактированием. Raw queries содержат bridge capabilities, а `Authorization` — bootstrap или session bearer credentials.
|
||||
- Если URI или headers содержат активную capability либо любой подлинный token, выпущенный текущим процессом, но запрос не соответствует carrier contract, Telemt отклоняет его локально. Такие credentials никогда не пересылаются в decoy. Просто канонически выглядящее поддельное значение остаётся обычным decoy traffic.
|
||||
- Сохраняйте один стабильный публичный адрес на vhost. Если DNS возвращает несколько ingress addresses, каждый deployment должен использовать адрес своего внешнего пути.
|
||||
- Bootstrap- и session-registries локальны для процесса. Для multi-process или multi-host upstream pool нужна affinity всего vhost: bridge GET, создание сессии, uplink, downlink и DELETE. Одному процессу Telemt дополнительная affinity не нужна.
|
||||
- Bootstrap- и session-registries локальны для процесса. Для multi-process или multi-host upstream pool нужна affinity всего vhost: исходный и recovery root GET, создание сессии, uplink, downlink, WebSocket Upgrade и DELETE. Одному процессу Telemt дополнительная affinity не нужна.
|
||||
- Неиспользованный bootstrap переживает reload конфигурации, только если остаётся активной точная identity профиля: host, `public_addr`, user, secret mode, carrier candidates, negotiation deadlines и capability. Уже созданные sessions сохраняют неизменные carrier и identity профиля и остаются lifecycle-bounded.
|
||||
- Decoy входит в anti-probing contract. До распространения ссылок проверьте через публичный TLS endpoint его обычный ответ 404 и response timing.
|
||||
|
||||
## Первичная проверка
|
||||
|
||||
1. Запустите пересобранный Telemt с WEB-конфигурацией и убедитесь, что приватный listener привязан.
|
||||
2. Через публичный TLS endpoint проверьте, что `GET /`, неизвестный path и некорректный query `bridge` возвращают настроенный decoy site.
|
||||
2. Через публичный TLS endpoint проверьте, что GET корня настроенного base, alias без завершающего слеша, неизвестный path внутри base и некорректный query `bridge` возвращают ожидаемый обычный сайт или настроенный decoy без синтезированного redirect. При prefix-only cohosting также убедитесь, что TLS-терминатор сохраняет base path побайтно.
|
||||
3. Убедитесь, что Telemt получает один корректно разбираемый адрес `X-Forwarded-For` и `Host: proxy.example.com` либо `Host: proxy.example.com:443`.
|
||||
4. Импортируйте напечатанную ссылку `tg://webproxy` в целевую сборку Telegram Desktop и установите соединение через прокси.
|
||||
5. Для `https-lanes` подтвердите согласование HTTP/2 на публичном connection и проверьте как минимум два одновременных logical streams; приватный hop к Telemt остаётся HTTP/1.1.
|
||||
6. Для `websocket` подтвердите один response `101`, binary relay traffic и RFC 6455 Ping/Pong после 25 секунд. Для `websocket-lanes` проверьте как минимум два одновременных stream sockets и убедитесь, что закрытие или повреждение одной lane не закрывает sibling или parent session.
|
||||
7. Проверьте reconnect и как минимум один long poll длительнее 25 секунд, чтобы frontend timeouts не обрывали carrier.
|
||||
7. Проверьте один HTTP replay и пересоздание session после scheduler gap, затем удерживайте long poll дольше 25 секунд, чтобы frontend timeouts не обрывали carrier.
|
||||
8. Проверяйте лимиты пользователя и logical MTProxy connections по logical-stream counters, а не по числу HTTP connections.
|
||||
9. При включённом auto-negotiation проверьте настроенную последовательность, replay точно той же попытки после намеренно потерянного response, terminal-поведение после commit и lifecycle rows `carrier_committed`/`carrier_healthy` в `/web-status`. Убедитесь, что нативный клиент без metadata использует фиксированный `carrier` без automatic response headers, а явные capabilities остаются неизменными.
|
||||
|
||||
@@ -370,10 +427,12 @@ curl -sS -X POST http://127.0.0.1:9091/v1/users/web-user/rotate-secret \
|
||||
| --- | --- |
|
||||
| WEB-конфигурация валидна на диске, но поведение listener’а не изменилось | Проверьте `deferred_process_fields`; listener и `[web.limits]` требуют перезапуска. |
|
||||
| Carrier-запросы попадают в decoy | Проверьте точный vhost, secret mode ссылки, CIDR непосредственного proxy и единственное корректно разбираемое значение `X-Forwarded-For`. |
|
||||
| Ссылка перестала работать после изменения `base_path` | Импортируйте заново напечатанную path-ссылку и убедитесь, что полный новый prefix без изменений попадает в Telemt. Существующие sessions могут восстановиться только через новый точный base; старые capabilities нельзя использовать повторно. |
|
||||
| `/telegram/web` перенаправляет на `/telegram/web/` | Добавьте точный non-WEB handler для path без слеша. В WEB-контракт Telemt входит только настроенное поддерево с завершающим слешем. |
|
||||
| Downlink `https-lanes`, участвующий в гонке, попадает в decoy с `404` | Убедитесь, что он начинается с `X-Down-Cursor: 0`, сохраняйте `X-Lane-ID` и задайте `lane_open_wait_secs` выше наблюдаемого разрыва down-before-`OPEN`. Продвинутый cursor отсутствующей lane намеренно закрывается fail-closed. |
|
||||
| Auto-negotiation переходит дальше после уже принятого трафика | Такое поведение некорректно. Проверьте аутентифицированный replay `X-Carrier-State` и lifecycle row commit carrier; ответ `committed` или `healthy` terminal и требует новой сессии. |
|
||||
| Long polls разрываются через фиксированный интервал | Поднимите client, server, send и read timeouts NGINX/HAProxy выше `web.timeouts.long_poll_secs`. |
|
||||
| WebSocket Upgrade попадает в decoy вместо `101` | Сохраните HTTP/1.1 `Connection: Upgrade`, `Upgrade: websocket`, единственный точный `Sec-WebSocket-Protocol` и канонический bodyless request `/api/v1/ws`. Также проверьте соответствие carrier/session и process connection reserve. |
|
||||
| WebSocket Upgrade попадает в decoy вместо `101` | Сохраните HTTP/1.1 `Connection: Upgrade`, `Upgrade: websocket`, единственный точный `Sec-WebSocket-Protocol` и канонический bodyless request по настроенному base плюс `/api/v1/ws`. Также проверьте соответствие carrier/session и process connection reserve. |
|
||||
| Один stream `websocket-lanes` закрылся, а siblings остались подключены | Это штатная failure boundary. Проверьте message/frame rows этой lane в `/web-status`; malformed, cross-lane, write-timeout и backend-close закрывают только затронутую lane. |
|
||||
| `/web-status` пуст | Убедитесь, что `[web.debug].enabled = true`, примените конфигурацию, выберите окно в пределах `max_window_secs` и создайте новый WEB-трафик после изменения policy. |
|
||||
| `https-lanes` работает, но streams всё ещё блокируют друг друга | Проверьте согласование публичного HTTP/2, сохранение `X-Lane-ID` и достаточное число upstream connections TLS-терминатора для параллельных приватных HTTP/1.1 polls. |
|
||||
|
||||
Reference in New Issue
Block a user