Compare commits

..

2 Commits

Author SHA1 Message Date
Alexey 6f2245952d Merge pull request #931 from telemt/3.5.8-docs
Docs 3.5.8 Pull-Up
2026-09-27 18:55:52 +03:00
Alexey 56e0f000ba Docs 3.5.8 Pull-Up
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
2026-09-27 18:55:31 +03:00
18 changed files with 1137 additions and 458 deletions
+55 -14
View File
@@ -56,7 +56,7 @@ net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 2000000 net.ipv4.tcp_max_tw_buckets = 2000000
``` ```
### 2.3 TCP Keepalive (Aggressive Dead Connection Culling) ### 2.3 TCP Keepalive (Aggressive Dead Connection Culling)
By default, Linux keeps silent, dropped connections open for over 2 hours. This consumes memory at scale. Configure the system to detect and drop them in < 5 minutes: By default, Linux keeps silent, dropped connections open for over 2 hours. This consumes memory at scale. The values below start probing after five idle minutes and abandon an unresponsive peer after the subsequent probe budget, roughly 7–8 minutes after it became idle:
```ini ```ini
net.ipv4.tcp_keepalive_time = 300 net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_intvl = 30
@@ -70,7 +70,7 @@ net.core.rmem_default = 262144
net.core.wmem_default = 262144 net.core.wmem_default = 262144
net.core.rmem_max = 16777216 net.core.rmem_max = 16777216
net.core.wmem_max = 16777216 net.core.wmem_max = 16777216
# TCP specific buffers (min, default, max) # TCP-specific buffers (min, default, max)
net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216
# Enable BBR # Enable BBR
@@ -96,17 +96,22 @@ net.netfilter.nf_conntrack_tcp_timeout_time_wait = 12
``` ```
*Note: Depending on your OS, you may need to run `modprobe nf_conntrack` before setting these parameters.* *Note: Depending on your OS, you may need to run `modprobe nf_conntrack` before setting these parameters.*
When `server.conntrack_control.inline_conntrack_control = true` and `[server.conntrack_control]` uses `notrack` or `hybrid`, one generation-fenced authority owns only the conntrack-control rules created by Telemt. It applies IPv4 and IPv6 changes, ignores stale or conflicting publications, and retries failed reconciliation after 1, 2, 4, 8, 16, then capped 30-second delays. A partial multi-command failure triggers best-effort restoration; failed rollback leaves the applied firewall state unknown until a later successful reconcile. `telemt_conntrack_control_state{flag="rule_apply_ok"}` reports whether the desired rule set is effective. With core telemetry enabled, reconcile and rollback attempts use `telemt_conntrack_rule_reconcile_total{result="success"|"error"}` and `telemt_conntrack_rule_rollback_total{result="success"|"error"}`. Shutdown performs a bounded 30-second best-effort cleanup of owned rules. Conntrack-control configuration remains restart-only.
--- ---
## 4. Multi-Tier Architecture: HAProxy Setup ## 4. Multi-Tier Architecture: HAProxy Setup
For massive traffic loads, buffering Telemt behind a reverse proxy like HAProxy can help absorb connection spikes and handle basic TCP connections before handing them off.
### HAProxy High-Load `haproxy.cfg` ### 4.1 Native MTProxy and TLS-front L4 deployment
For massive native MTProxy or TLS-front traffic, an L4 HAProxy can absorb connection spikes before handing TCP streams to Telemt. The following example is **not valid for a WEB listener**.
#### HAProxy High-Load `haproxy.cfg`
```haproxy ```haproxy
global global
# Disable detailed logging under load # Disable detailed connection logs under load
log stdout format raw local0 err log stdout format raw local0 err
# maxconn 250000 maxconn 250000
# Tune buffers and socket acceptance
# Buffer tuning
tune.bufsize 16384 tune.bufsize 16384
tune.maxaccept 64 tune.maxaccept 64
defaults defaults
@@ -117,7 +122,7 @@ defaults
timeout connect 5s timeout connect 5s
timeout client 1h timeout client 1h
timeout server 1h timeout server 1h
# Quick purge for dead peers # Purge dead peers quickly
timeout client-fin 10s timeout client-fin 10s
timeout server-fin 10s timeout server-fin 10s
frontend proxy_in frontend proxy_in
@@ -127,15 +132,51 @@ frontend proxy_in
default_backend telemt_backend default_backend telemt_backend
backend telemt_backend backend telemt_backend
option tcp-smart-connect option tcp-smart-connect
# Send-Proxy-V2 to preserve Client IP for Telemt's internal logic # Preserve the client IP for Telemt through PROXY v2
server telemt_core 10.10.10.1:443 maxconn 250000 send-proxy-v2 check inter 5s server telemt_core 10.10.10.1:443 maxconn 250000 send-proxy-v2 check inter 5s
``` ```
**Important**: Telemt must be configured to process the `PROXY` protocol on port `443` for this chain to work and preserve client IPs. **Important**: Telemt must be configured to process the `PROXY` protocol on port `443` for this chain to work and preserve client IPs.
### 4.2 WEB deployment
WEB mode requires an L7 TLS terminator and a private plain HTTP/1.1 Telemt listener with `proxy_protocol = false`; do not reuse the L4 `send-proxy-v2` backend above. Follow the complete [WEB proxy guide](../WEB/WEB_PROXY.en.md) and preserve `Host`, the exact path and query, WebSocket Upgrade headers, and a single overwritten `X-Forwarded-For` value from explicitly trusted terminator CIDRs. Public ALPN must offer `h2` for `https-lanes` and `http/1.1` for WebSocket Upgrade.
Set the WEB listener to `web_client_ip_source = "x_forwarded_for"` and list only the immediate HAProxy addresses in `web_trusted_proxy_cidrs`. Never trust a client-reachable subnet and never enable PROXY protocol on this listener.
Route the complete vhost to one Telemt process. For prefix cohosting, preserve the configured `base_path` without rewrite; while migrating it, route both old and new subtrees to Telemt until old process-issued credentials can no longer be used. Multi-process backends require whole-vhost affinity for the bridge root, session creation, recovery, uplink, downlink, DELETE, diagnostics, and WebSocket Upgrade.
For example, this HAProxy fragment routes only one exact host and slash-terminated WEB subtree without changing the request target:
```haproxy
frontend https_in
mode http
bind *:443 ssl crt /etc/haproxy/certs/proxy.pem alpn h2,http/1.1
timeout client 65s
acl telemt_web_host hdr(host) -i proxy.example.com proxy.example.com:443
acl telemt_web_path path_beg /telegram/web/
use_backend telemt_web if telemt_web_host telemt_web_path
backend telemt_web
mode http
retries 0
timeout connect 5s
timeout server 65s
http-request set-header Host proxy.example.com
http-request set-header X-Forwarded-For %[src]
server telemt_web_1 127.0.0.1:18080 check
```
Handle the no-slash `/telegram/web` path outside this backend so the frontend cannot synthesize a redirect alias into the authenticated subtree. Do not add `set-path`, `replace-path`, or a path component to the backend server URL. The 65-second values are examples for the defaults; configure client and server timeouts above `web.timeouts.long_poll_secs` and twice the effective WebSocket liveness interval.
Capacity planning must include both public TLS sockets and private terminator-to-Telemt sockets. Size file descriptors, terminator upstream capacity, `web.limits.max_http_connections`, handler capacity, concurrent long polls, and WebSocket lanes together; an upstream keepalive pool is not a concurrency limit.
--- ---
## 5. Diagnostics & Monitoring ## 5. Diagnostics & Monitoring
When operating under load, these commands are useful for diagnostics: When operating under load, these commands are useful for diagnostics:
* **Checking dropped connections (Queues full)**: `netstat -s | grep "times the listen queue of a socket overflowed"`
* **Checking Conntrack drops**: `dmesg | grep conntrack` - **Listen queue drops**: inspect `ListenOverflows` and `ListenDrops` in `/proc/net/netstat` or `nstat`.
* **Checking File Descriptor usage**: `cat /proc/sys/fs/file-nr` - **Conntrack pressure**: inspect `nf_conntrack_count`, kernel logs, and `telemt_conntrack_control_state{flag="rule_apply_ok"}`; with core telemetry enabled, also inspect the reconcile/rollback counters above.
* **Real-time connection states**: `ss -s` (Avoid using `netstat` on heavy loads). - **File descriptor usage**: `cat /proc/sys/fs/file-nr` and the Telemt process limits under `/proc/<pid>/limits`.
- **Connection states**: `ss -s`; avoid full `netstat` scans on a busy host.
- **Rate limiter contention**: with core telemetry enabled, alert on a positive counter increase or rate, for example `increase(telemt_rate_limiter_cas_retry_exhausted_total[5m]) > 0`, grouped by `scope`, `direction`, and `operation`. Reserve exhaustion returns a zero grant without classifying it as a configured throttle; refund exhaustion retains the charge. This metric is neither a connection-drop counter nor a policy-throttle counter.
- **WEB**: combine terminator telemetry and an external TLS probe with `/v1/runtime/web/status`, `telemt_web_tcp_accept_total{result="accepted"|"error"}`, and the other `telemt_web_*` metrics. Request paths and `base_path` are intentionally not metric labels.
+60 -18
View File
@@ -56,7 +56,7 @@ net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 2000000 net.ipv4.tcp_max_tw_buckets = 2000000
``` ```
### 2.3 TCP Keepalive (Агрессивная очистка мертвых соединений) ### 2.3 TCP Keepalive (Агрессивная очистка мертвых соединений)
По умолчанию Linux держит "оборванные" TCP-сессии более 2 часов. Задайте параметры для обнаружения и сброса мертвых соединений за менее чем 5 минут: По умолчанию Linux держит "оборванные" TCP-сессии более 2 часов. Значения ниже начинают probes после пяти минут простоя и закрывают не отвечающий peer после последующего probe budget — примерно через 7–8 минут общего простоя:
```ini ```ini
net.ipv4.tcp_keepalive_time = 300 net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_intvl = 30
@@ -65,15 +65,15 @@ net.ipv4.tcp_keepalive_probes = 5
### 2.4 Буферы TCP и управление перегрузками (Congestion Control) ### 2.4 Буферы TCP и управление перегрузками (Congestion Control)
Оптимизируйте использование памяти на сокет и переключитесь на алгоритм BBR (Bottleneck Bandwidth and Round-trip propagation time) для улучшения задержки на плохих сетях: Оптимизируйте использование памяти на сокет и переключитесь на алгоритм BBR (Bottleneck Bandwidth and Round-trip propagation time) для улучшения задержки на плохих сетях:
```ini ```ini
# Размеры буферов ядра (по умолчанию и макс) # Core buffer sizes
net.core.rmem_default = 262144 net.core.rmem_default = 262144
net.core.wmem_default = 262144 net.core.wmem_default = 262144
net.core.rmem_max = 16777216 net.core.rmem_max = 16777216
net.core.wmem_max = 16777216 net.core.wmem_max = 16777216
# Специфичные TCP буферы (min, default, max) # TCP-specific buffers (min, default, max)
net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216
# Включение BBR # Enable BBR
net.core.default_qdisc = fq net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr net.ipv4.tcp_congestion_control = bbr
``` ```
@@ -88,23 +88,30 @@ sysctl net.netfilter.nf_conntrack_count
``` ```
Если вы близки к пределу, увеличьте таблицу и заставьте ядро быстрее удалять установленные соединения. Добавьте в `/etc/sysctl.d/99-telemt-highload.conf`: Если вы близки к пределу, увеличьте таблицу и заставьте ядро быстрее удалять установленные соединения. Добавьте в `/etc/sysctl.d/99-telemt-highload.conf`:
```ini ```ini
# In /etc/sysctl.d/99-telemt-highload.conf
net.netfilter.nf_conntrack_max = 2097152 net.netfilter.nf_conntrack_max = 2097152
# Снижаем таймаут с дефолтных 5 дней до 1 часа # Reduce timeout from default 5 days to 1 hour
net.netfilter.nf_conntrack_tcp_timeout_established = 3600 net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 12 net.netfilter.nf_conntrack_tcp_timeout_time_wait = 12
``` ```
*Внимание: в зависимости от ОС, вам может потребоваться выполнить `modprobe nf_conntrack` перед установкой этих параметров.* *Внимание: в зависимости от ОС, вам может потребоваться выполнить `modprobe nf_conntrack` перед установкой этих параметров.*
Когда `server.conntrack_control.inline_conntrack_control = true` и `[server.conntrack_control]` использует `notrack` или `hybrid`, один generation-fenced authority владеет только правилами conntrack control, созданными Telemt. Он применяет изменения IPv4 и IPv6, игнорирует устаревшие или конфликтующие публикации и повторяет неудачный reconcile через 1, 2, 4, 8, 16 и далее не более чем 30 секунд. Частичный многошаговый сбой запускает best-effort восстановление; неудачный rollback оставляет applied firewall state неизвестным до следующего успешного reconcile. `telemt_conntrack_control_state{flag="rule_apply_ok"}` показывает, действует ли требуемый набор правил. При включённой core telemetry попытки используют `telemt_conntrack_rule_reconcile_total{result="success"|"error"}` и `telemt_conntrack_rule_rollback_total{result="success"|"error"}`. При shutdown выполняется ограниченный 30 секундами best-effort cleanup собственных правил. Настройки conntrack control требуют перезапуска.
--- ---
## 4. Архитектура: Развертывание за HAProxy ## 4. Архитектура: развёртывание за HAProxy
Для максимальных нагрузок выставление Telemt напрямую в интернет менее эффективно, чем использование оптимизированного L4-балансировщика. HAProxy эффективен в поглощении TCP атак, обработке рукопожатий и сглаживании всплесков подключений.
### Оптимизация `haproxy.cfg` для High-Load ### 4.1 L4-развёртывание native MTProxy и TLS-front
Для массового native MTProxy- или TLS-front-трафика L4 HAProxy может поглощать всплески соединений перед передачей TCP streams в Telemt. Следующий пример **не подходит для WEB-listener**.
#### Оптимизация `haproxy.cfg` для High-Load
```haproxy ```haproxy
global global
# Отключить детальные логи соединений под нагрузкой # Disable detailed connection logs under load
log stdout format raw local0 err log stdout format raw local0 err
maxconn 250000 maxconn 250000
# Тюнинг буферов и приема сокетов # Tune buffers and socket acceptance
tune.bufsize 16384 tune.bufsize 16384
tune.maxaccept 64 tune.maxaccept 64
defaults defaults
@@ -115,7 +122,7 @@ defaults
timeout connect 5s timeout connect 5s
timeout client 1h timeout client 1h
timeout server 1h timeout server 1h
# Быстрая очистка мертвых пиров # Purge dead peers quickly
timeout client-fin 10s timeout client-fin 10s
timeout server-fin 10s timeout server-fin 10s
frontend proxy_in frontend proxy_in
@@ -125,15 +132,50 @@ frontend proxy_in
default_backend telemt_backend default_backend telemt_backend
backend telemt_backend backend telemt_backend
option tcp-smart-connect option tcp-smart-connect
# Send-Proxy-V2 обязателен для сохранения IP клиента внутри внутренней логики Telemt # Preserve the client IP for Telemt through PROXY v2
server telemt_core 10.10.10.1:443 maxconn 250000 send-proxy-v2 check inter 5s server telemt_core 10.10.10.1:443 maxconn 250000 send-proxy-v2 check inter 5s
``` ```
**Важно**: Telemt должен быть настроен на обработку протокола `PROXY` на порту `443`, чтобы получать оригинальные IP-адреса клиентов. **Важно**: Telemt должен быть настроен на обработку протокола `PROXY` на порту `443`, чтобы получать оригинальные IP-адреса клиентов.
### 4.2 WEB-развёртывание
WEB-режиму требуется L7 TLS-терминатор и приватный plain HTTP/1.1 listener Telemt с `proxy_protocol = false`; не используйте для него L4 backend с `send-proxy-v2` из предыдущего примера. Следуйте полному [руководству по WEB-прокси](../WEB/WEB_PROXY.ru.md), сохраняйте `Host`, точные path и query, WebSocket Upgrade headers и передавайте один перезаписанный `X-Forwarded-For` только от явно доверенных CIDR терминатора. Public ALPN должен предлагать `h2` для `https-lanes` и `http/1.1` для WebSocket Upgrade.
Для WEB-listener задайте `web_client_ip_source = "x_forwarded_for"` и укажите в `web_trusted_proxy_cidrs` только адреса непосредственного HAProxy. Не доверяйте доступной клиентам подсети и не включайте PROXY protocol на этом listener.
Направляйте весь vhost в один процесс Telemt. При совместном размещении по prefix сохраняйте настроенный `base_path` без rewrite; во время миграции направляйте в Telemt старое и новое поддеревья, пока ранее выпущенные процессом credentials ещё могут использоваться. Multi-process backend требует affinity всего vhost для bridge root, создания и восстановления session, uplink, downlink, DELETE, diagnostics и WebSocket Upgrade.
Например, следующий фрагмент HAProxy направляет только точный host и WEB-поддерево с завершающим слешем, не изменяя request target:
```haproxy
frontend https_in
mode http
bind *:443 ssl crt /etc/haproxy/certs/proxy.pem alpn h2,http/1.1
timeout client 65s
acl telemt_web_host hdr(host) -i proxy.example.com proxy.example.com:443
acl telemt_web_path path_beg /telegram/web/
use_backend telemt_web if telemt_web_host telemt_web_path
backend telemt_web
mode http
retries 0
timeout connect 5s
timeout server 65s
http-request set-header Host proxy.example.com
http-request set-header X-Forwarded-For %[src]
server telemt_web_1 127.0.0.1:18080 check
```
Обрабатывайте путь без завершающего слеша `/telegram/web` вне этого backend, чтобы frontend не создавал redirect alias в аутентифицированное поддерево. Не добавляйте `set-path`, `replace-path` или path-компонент к адресу backend server. Значения 65 секунд — пример для defaults; client и server timeouts должны быть больше `web.timeouts.long_poll_secs` и удвоенного effective WebSocket liveness interval.
При расчёте capacity учитывайте одновременно публичные TLS sockets и приватные sockets между терминатором и Telemt. Совместно рассчитывайте file descriptors, upstream capacity терминатора, `web.limits.max_http_connections`, handler capacity, параллельные long polls и WebSocket lanes; upstream keepalive pool не является лимитом concurrency.
--- ---
## 5. Диагностика ## 5. Диагностика и мониторинг
Команды для выявления узких мест:
* **Проверка дропов TCP (переполнение очередей)**: `netstat -s | grep "times the listen queue of a socket overflowed"` - **Переполнение listen queues**: проверяйте `ListenOverflows` и `ListenDrops` в `/proc/net/netstat` либо через `nstat`.
* **Контроль отбрасывания пакетов Conntrack**: `dmesg | grep conntrack` - **Conntrack pressure**: проверяйте `nf_conntrack_count`, kernel logs и `telemt_conntrack_control_state{flag="rule_apply_ok"}`; при включённой core telemetry также проверяйте счётчики reconcile/rollback выше.
* **Проверка использования файловых дескрипторов**: `cat /proc/sys/fs/file-nr` - **File descriptors**: `cat /proc/sys/fs/file-nr` и лимиты процесса Telemt в `/proc/<pid>/limits`.
* **Отображение состояния сокетов**: `ss -s` (Избегайте использования `netstat` под высокой нагрузкой). - **Состояния соединений**: `ss -s`; избегайте полного сканирования через `netstat` на нагруженном сервере.
- **Rate limiter contention**: при включённой core telemetry настройте alert на положительный прирост или rate counter, например `increase(telemt_rate_limiter_cas_retry_exhausted_total[5m]) > 0`, с группировкой по `scope`, `direction` и `operation`. Reserve exhaustion возвращает нулевой grant без классификации как настроенный throttle; refund exhaustion сохраняет списание. Эта метрика не является ни счётчиком dropped connections, ни счётчиком policy throttling.
- **WEB**: объединяйте telemetry терминатора и внешний TLS probe с `/v1/runtime/web/status`, `telemt_web_tcp_accept_total{result="accepted"|"error"}` и остальными `telemt_web_*` metrics. Request path и `base_path` намеренно не используются как metric labels.
+28 -10
View File
@@ -17,7 +17,7 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
| `general.use_middle_proxy` | `bool` | `true` | keine | Aktiviert den ME-Transportmodus. Bei `false` wird Direct-Modus verwendet. | `use_middle_proxy = true` | | `general.use_middle_proxy` | `bool` | `true` | keine | Aktiviert den ME-Transportmodus. Bei `false` wird Direct-Modus verwendet. | `use_middle_proxy = true` |
| `general.proxy_secret_path` | `Option<String>` | `"proxy-secret"` | Pfad kann `null` sein | Pfad zur Telegram-Infrastrukturdatei `proxy-secret`. | `proxy_secret_path = "proxy-secret"` | | `general.proxy_secret_path` | `Option<String>` | `"proxy-secret"` | Pfad kann `null` sein | Pfad zur Telegram-Infrastrukturdatei `proxy-secret`. | `proxy_secret_path = "proxy-secret"` |
| `general.middle_proxy_nat_ip` | `Option<IpAddr>` | `null` | gültige IP bei gesetztem Wert | Manueller Override der öffentlichen NAT-IP für ME-Adressmaterial. | `middle_proxy_nat_ip = "203.0.113.10"` | | `general.middle_proxy_nat_ip` | `Option<IpAddr>` | `null` | gültige IP bei gesetztem Wert | Manueller Override der öffentlichen NAT-IP für ME-Adressmaterial. | `middle_proxy_nat_ip = "203.0.113.10"` |
| `general.middle_proxy_nat_probe` | `bool` | `true` | wird auf `true` erzwungen, wenn `use_middle_proxy=true` | Aktiviert NAT-Probing für ME. | `middle_proxy_nat_probe = true` | | `general.middle_proxy_nat_probe` | `bool` | `true` | keine | Aktiviert NAT-Probing, wenn ME-Modus und `network.stun_use` beide aktiviert sind. | `middle_proxy_nat_probe = true` |
| `general.stun_nat_probe_concurrency` | `usize` | `8` | muss `> 0` sein | Maximale parallele STUN-Probes während NAT-Erkennung. | `stun_nat_probe_concurrency = 16` | | `general.stun_nat_probe_concurrency` | `usize` | `8` | muss `> 0` sein | Maximale parallele STUN-Probes während NAT-Erkennung. | `stun_nat_probe_concurrency = 16` |
| `network.stun_use` | `bool` | `true` | keine | Globaler STUN-Schalter. Bei `false` wird STUN deaktiviert. | `stun_use = true` | | `network.stun_use` | `bool` | `true` | keine | Globaler STUN-Schalter. Bei `false` wird STUN deaktiviert. | `stun_use = true` |
| `network.stun_servers` | `Vec<String>` | integrierter öffentlicher Pool | Duplikate/leer werden entfernt | Primäre STUN-Serverliste für NAT/Public-Endpoint-Erkennung. | `stun_servers = ["stun1.l.google.com:19302"]` | | `network.stun_servers` | `Vec<String>` | integrierter öffentlicher Pool | Duplikate/leer werden entfernt | Primäre STUN-Serverliste für NAT/Public-Endpoint-Erkennung. | `stun_servers = ["stun1.l.google.com:19302"]` |
@@ -31,11 +31,11 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
| Parameter | Typ | Default | Einschränkungen / Validierung | Laufzeiteffekt | Beispiel | | Parameter | Typ | Default | Einschränkungen / Validierung | Laufzeiteffekt | Beispiel |
|---|---|---:|---|---|---| |---|---|---:|---|---|---|
| `general.middle_proxy_pool_size` | `usize` | `8` | keine | Zielgröße des aktiven ME-Writer-Pools. | `middle_proxy_pool_size = 12` | | `general.middle_proxy_pool_size` | `usize` | `8` | keine | Nicht erzwingender Kompatibilitäts-/Startup-Log-Wert; aktive Writer-Ziele stammen aus der DC-Family-Floor-Policy. | `middle_proxy_pool_size = 12` |
| `general.middle_proxy_warm_standby` | `usize` | `16` | keine | Reserviertes Kompatibilitätsfeld in der aktuellen Revision (kein aktiver Runtime-Consumer). | `middle_proxy_warm_standby = 16` | | `general.middle_proxy_warm_standby` | `usize` | `16` | keine | Reserviertes Kompatibilitätsfeld in der aktuellen Revision (kein aktiver Runtime-Consumer). | `middle_proxy_warm_standby = 16` |
| `general.me_keepalive_enabled` | `bool` | `true` | keine | Aktiviert periodischen ME-Keepalive/Ping-Traffic. | `me_keepalive_enabled = true` | | `general.me_keepalive_enabled` | `bool` | `true` | keine | Aktiviert periodischen ME-Keepalive/Ping-Traffic. | `me_keepalive_enabled = true` |
| `general.me_keepalive_interval_secs` | `u64` | `25` | keine | Basisintervall für Keepalive (Sekunden). | `me_keepalive_interval_secs = 20` | | `general.me_keepalive_interval_secs` | `u64` | `8` | keine | Basisintervall für Keepalive (Sekunden). | `me_keepalive_interval_secs = 20` |
| `general.me_keepalive_jitter_secs` | `u64` | `5` | keine | Keepalive-Jitter zur Vermeidung synchroner Peaks. | `me_keepalive_jitter_secs = 3` | | `general.me_keepalive_jitter_secs` | `u64` | `2` | keine | Keepalive-Jitter zur Vermeidung synchroner Peaks. | `me_keepalive_jitter_secs = 3` |
| `general.me_keepalive_payload_random` | `bool` | `true` | keine | Randomisiert Keepalive-Payload-Bytes. | `me_keepalive_payload_random = true` | | `general.me_keepalive_payload_random` | `bool` | `true` | keine | Randomisiert Keepalive-Payload-Bytes. | `me_keepalive_payload_random = true` |
| `general.me_warmup_stagger_enabled` | `bool` | `true` | keine | Aktiviert gestaffeltes Warmup zusätzlicher ME-Verbindungen. | `me_warmup_stagger_enabled = true` | | `general.me_warmup_stagger_enabled` | `bool` | `true` | keine | Aktiviert gestaffeltes Warmup zusätzlicher ME-Verbindungen. | `me_warmup_stagger_enabled = true` |
| `general.me_warmup_step_delay_ms` | `u64` | `500` | keine | Basisverzögerung zwischen Warmup-Schritten (ms). | `me_warmup_step_delay_ms = 300` | | `general.me_warmup_step_delay_ms` | `u64` | `500` | keine | Basisverzögerung zwischen Warmup-Schritten (ms). | `me_warmup_step_delay_ms = 300` |
@@ -44,6 +44,7 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
| `general.me_reconnect_backoff_base_ms` | `u64` | `500` | keine | Initiales Reconnect-Backoff (ms). | `me_reconnect_backoff_base_ms = 250` | | `general.me_reconnect_backoff_base_ms` | `u64` | `500` | keine | Initiales Reconnect-Backoff (ms). | `me_reconnect_backoff_base_ms = 250` |
| `general.me_reconnect_backoff_cap_ms` | `u64` | `30000` | keine | Maximales Reconnect-Backoff (ms). | `me_reconnect_backoff_cap_ms = 10000` | | `general.me_reconnect_backoff_cap_ms` | `u64` | `30000` | keine | Maximales Reconnect-Backoff (ms). | `me_reconnect_backoff_cap_ms = 10000` |
| `general.me_reconnect_fast_retry_count` | `u32` | `16` | keine | Budget für Sofort-Retries vor längerem Backoff. | `me_reconnect_fast_retry_count = 8` | | `general.me_reconnect_fast_retry_count` | `u32` | `16` | keine | Budget für Sofort-Retries vor längerem Backoff. | `me_reconnect_fast_retry_count = 8` |
| `general.me_writer_byte_budget_bytes` | `usize` | `33570816` | Vielfaches von `16384`; dynamisches Minimum für `max_client_frame`; Maximum `268435456` | Begrenzte Byte-Permits für die ausgehende Staging-Queue jedes ME-Writers. | `me_writer_byte_budget_bytes = 33570816` |
### 3) Reinit/Hardswap, Secret-Rotation und Degradation ### 3) Reinit/Hardswap, Secret-Rotation und Degradation
@@ -51,6 +52,10 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
|---|---|---:|---|---|---| |---|---|---:|---|---|---|
| `general.hardswap` | `bool` | `true` | keine | Aktiviert generation-basierte Hardswap-Strategie für den ME-Pool. | `hardswap = true` | | `general.hardswap` | `bool` | `true` | keine | Aktiviert generation-basierte Hardswap-Strategie für den ME-Pool. | `hardswap = true` |
| `general.me_reinit_every_secs` | `u64` | `900` | muss `> 0` sein | Intervall für periodische ME-Reinitialisierung. | `me_reinit_every_secs = 600` | | `general.me_reinit_every_secs` | `u64` | `900` | muss `> 0` sein | Intervall für periodische ME-Reinitialisierung. | `me_reinit_every_secs = 600` |
| `general.me_reinit_singleflight` | `bool` | `true` | keine | Serialisiert Reinit-Zyklen aus allen Triggerquellen. | `me_reinit_singleflight = true` |
| `general.me_reinit_max_concurrency` | `usize` | `2` | Bereich `[1,8]`; effektiver Wert `1` bei Singleflight | Begrenzt parallele Generation-Warmups; weitere Trigger werden zu einem Rerun zusammengeführt. | `me_reinit_max_concurrency = 2` |
| `general.me_reinit_trigger_channel` | `usize` | `64` | Bereich `[1,4096]` | Begrenzt eingereihte Reinit-Trigger in jeder Runtime-Generation. | `me_reinit_trigger_channel = 64` |
| `general.me_reinit_coalesce_window_ms` | `u64` | `200` | keine | Fasst Trigger-Bursts vor einem Reinit-Zyklus zusammen. | `me_reinit_coalesce_window_ms = 200` |
| `general.me_hardswap_warmup_delay_min_ms` | `u64` | `1000` | muss `<= me_hardswap_warmup_delay_max_ms` sein | Untere Grenze für Warmup-Dial-Abstände. | `me_hardswap_warmup_delay_min_ms = 500` | | `general.me_hardswap_warmup_delay_min_ms` | `u64` | `1000` | muss `<= me_hardswap_warmup_delay_max_ms` sein | Untere Grenze für Warmup-Dial-Abstände. | `me_hardswap_warmup_delay_min_ms = 500` |
| `general.me_hardswap_warmup_delay_max_ms` | `u64` | `2000` | muss `> 0` sein | Obere Grenze für Warmup-Dial-Abstände. | `me_hardswap_warmup_delay_max_ms = 1200` | | `general.me_hardswap_warmup_delay_max_ms` | `u64` | `2000` | muss `> 0` sein | Obere Grenze für Warmup-Dial-Abstände. | `me_hardswap_warmup_delay_max_ms = 1200` |
| `general.me_hardswap_warmup_extra_passes` | `u8` | `3` | Bereich `[0,10]` | Zusätzliche Warmup-Pässe nach dem Basispass. | `me_hardswap_warmup_extra_passes = 2` | | `general.me_hardswap_warmup_extra_passes` | `u8` | `3` | Bereich `[0,10]` | Zusätzliche Warmup-Pässe nach dem Basispass. | `me_hardswap_warmup_extra_passes = 2` |
@@ -61,12 +66,20 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
| `general.proxy_secret_rotate_runtime` | `bool` | `true` | keine | Aktiviert Runtime-Rotation des Proxy-Secrets. | `proxy_secret_rotate_runtime = true` | | `general.proxy_secret_rotate_runtime` | `bool` | `true` | keine | Aktiviert Runtime-Rotation des Proxy-Secrets. | `proxy_secret_rotate_runtime = true` |
| `general.proxy_secret_len_max` | `usize` | `256` | Bereich `[32,4096]` | Obergrenze für akzeptierte Secret-Länge. | `proxy_secret_len_max = 512` | | `general.proxy_secret_len_max` | `usize` | `256` | Bereich `[32,4096]` | Obergrenze für akzeptierte Secret-Länge. | `proxy_secret_len_max = 512` |
| `general.update_every` | `Option<u64>` | `300` | wenn gesetzt: `> 0`; bei `null`: Legacy-Min-Fallback | Einheitliches Refresh-Intervall für ME-Config + Secret-Updater. | `update_every = 300` | | `general.update_every` | `Option<u64>` | `300` | wenn gesetzt: `> 0`; bei `null`: Legacy-Min-Fallback | Einheitliches Refresh-Intervall für ME-Config + Secret-Updater. | `update_every = 300` |
| `general.me_pool_drain_ttl_secs` | `u64` | `90` | keine | Zeitraum, in dem stale Writer noch als Fallback zulässig sind. | `me_pool_drain_ttl_secs = 120` | | `general.me_pool_drain_ttl_secs` | `u64` | `90` | keine | Altersschwelle für Warnungen bei langem Drain und Untergrenze der Force-Close-Normalisierung; gewährt keine stale Binds. | `me_pool_drain_ttl_secs = 120` |
| `general.me_pool_min_fresh_ratio` | `f32` | `0.8` | Bereich `[0.0,1.0]` | Coverage-Schwelle vor Drain der alten Generation. | `me_pool_min_fresh_ratio = 0.9` | | `general.me_bind_stale_mode` | `"never"`, `"ttl"` oder `"always"` | `"never"` | keine | Steuert neue Bindings auf draining stale Writer für nicht abgedeckte DC-Family-Gruppen. | `me_bind_stale_mode = "never"` |
| `general.me_reinit_drain_timeout_secs` | `u64` | `120` | `0` = kein Force-Close; wenn `>0 && < TTL`, dann auf TTL angehoben | Force-Close-Timeout für draining stale Writer. | `me_reinit_drain_timeout_secs = 0` | | `general.me_bind_stale_ttl_secs` | `u64` | `90` | keine | Stale-Bind-Fenster nur für `me_bind_stale_mode = "ttl"`; `0` deaktiviert den TTL-Ablauf für zulässige draining Writer. | `me_bind_stale_ttl_secs = 90` |
| `general.me_pool_min_fresh_ratio` | `f32` | `0.8` | Bereich `[0.0,1.0]` | Mindestverhältnis frischer DC-Family-Coverage beim Commit. | `me_pool_min_fresh_ratio = 0.9` |
| `general.me_reinit_drain_timeout_secs` | `u64` | `90` | `0` nutzt den 300-Sekunden-Sicherheitsfallback; ein effektiver Wert unter der Drain-TTL wird auf die TTL angehoben | Force-Close-Timeout für draining stale Writer. | `me_reinit_drain_timeout_secs = 0` |
| `general.auto_degradation_enabled` | `bool` | `true` | keine | Reserviertes Kompatibilitätsfeld in aktueller Revision (kein aktiver Runtime-Consumer). | `auto_degradation_enabled = true` | | `general.auto_degradation_enabled` | `bool` | `true` | keine | Reserviertes Kompatibilitätsfeld in aktueller Revision (kein aktiver Runtime-Consumer). | `auto_degradation_enabled = true` |
| `general.degradation_min_unavailable_dc_groups` | `u8` | `2` | keine | Reservierter Kompatibilitäts-Schwellenwert in aktueller Revision (kein aktiver Runtime-Consumer). | `degradation_min_unavailable_dc_groups = 2` | | `general.degradation_min_unavailable_dc_groups` | `u8` | `2` | keine | Reservierter Kompatibilitäts-Schwellenwert in aktueller Revision (kein aktiver Runtime-Consumer). | `degradation_min_unavailable_dc_groups = 2` |
Eine Hardswap-Kandidatengeneration ist nur für denselben Desired-Map-Hash und dieselbe Endpoint-Revision maßgeblich. Wiederholte Versuche verwenden diese pending Generation höchstens 1800 Sekunden wieder; danach wird eine neue Generation angelegt. Der Commit validiert die Authority erneut und verlangt mindestens `me_pool_min_fresh_ratio` frische DC-Family-Coverage. Fehlende Gruppen blockieren den Commit bei `me_bind_stale_mode = "never"`; `ttl` oder `always` können mit policy-begrenztem stale Fallback für diese Gruppen committen. Abgedeckte alte Writer können sofort retiret werden; Hardswap ist daher ein atomarer Policy-Übergang, keine universelle Zero-Drop-Garantie.
Writer-Replacement verwendet separat den cancellation-sicheren Zustand `Open -> Preparing -> Retiring`. `Preparing` verhindert doppelte Replacement-Arbeit, erlaubt aber weiterhin neue Binds. Unter dem Registry-Binding-Guard validiert der Commit das Opfer erneut, setzt es auf `Retiring`, um neue Binds zu blockieren, installiert und publiziert den Nachfolger und beginnt das Draining des Vorgängers, bevor der Guard freigegeben wird. Das Verwerfen einer Reservation vor dieser Commit-Grenze stellt `Open` wieder her.
Pending-Alter, Writer-Anzahl und -Defizit, fehlende DC-Gruppen, Map-Aktualität, verwaiste Warm-Writer und die Phasen `preparing`/`retiring` sind über `/v1/runtime/me_pool_state` sichtbar. Die entsprechenden Gauges `telemt_me_hardswap_*` und `telemt_me_writer_replacement_current` liefern Null, wenn ME-Telemetrie `silent` ist oder kein aktiver ME-Snapshot vorliegt. In Prometheus muss `telemt_me_hardswap_pending_map_current` zusammen mit `telemt_me_hardswap_pending` ausgewertet werden: Map-Currency Null bedeutet auch, dass keine pending Generation existiert; die API verwendet dann `null`. Alarmieren Sie bei einem Pending-Alter nahe 1800 Sekunden, dauerhaftem Defizit oder fehlenden Gruppen, stale Map, verwaisten Warm-Writern oder nicht konvergierenden Replacement-Phasen.
## Deprecated / Legacy Parameter ## Deprecated / Legacy Parameter
| Parameter | Status | Ersatz | Aktuelles Verhalten | Migrationshinweis | | Parameter | Status | Ersatz | Aktuelles Verhalten | Migrationshinweis |
@@ -113,8 +126,8 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
- zuerst `bind_addresses` (nur gleiche IP-Familie wie Target); - zuerst `bind_addresses` (nur gleiche IP-Familie wie Target);
- bei `interface` (Name) + `bind_addresses` wird jede Candidate-IP gegen Interface-Adressen validiert; - bei `interface` (Name) + `bind_addresses` wird jede Candidate-IP gegen Interface-Adressen validiert;
- ungültige Kandidaten werden mit `WARN` verworfen; - ungültige Kandidaten werden mit `WARN` verworfen;
- bleiben keine gültigen Kandidaten übrig, erfolgt unbound direct connect (`bind_ip=None`); - lässt eine nicht leere `bind_addresses`-Liste keinen gültigen Candidate derselben Familie übrig, schlägt die Verbindung mit einem Konfigurationsfehler fail-closed fehl;
- wenn `bind_addresses` nicht passt, wird `interface` verwendet (Literal-IP oder Interface-Primäradresse). - nur wenn `bind_addresses` fehlt oder leer ist, wird `interface` verwendet (Literal-IP oder Interface-Primäradresse); ergibt auch dies keine Adresse, bleibt Direct Connect ungebunden.
6. Für `socks4/socks5` mit Hostname-`address` ist Interface-Binding nicht unterstützt und wird mit Warnung ignoriert. 6. Für `socks4/socks5` mit Hostname-`address` ist Interface-Binding nicht unterstützt und wird mit Warnung ignoriert.
7. Runtime DNS Overrides werden für Hostname-Auflösung bei Upstream-Verbindungen genutzt. 7. Runtime DNS Overrides werden für Hostname-Auflösung bei Upstream-Verbindungen genutzt.
8. Im ME-Modus wird der gewählte Upstream auch für den ME-TCP-Dial-Pfad verwendet. 8. Im ME-Modus wird der gewählte Upstream auch für den ME-TCP-Dial-Pfad verwendet.
@@ -196,7 +209,6 @@ use_middle_proxy = true
proxy_secret_path = "proxy-secret" proxy_secret_path = "proxy-secret"
middle_proxy_nat_probe = true middle_proxy_nat_probe = true
stun_nat_probe_concurrency = 16 stun_nat_probe_concurrency = 16
middle_proxy_pool_size = 12
me_keepalive_enabled = true me_keepalive_enabled = true
me_keepalive_interval_secs = 20 me_keepalive_interval_secs = 20
me_keepalive_jitter_secs = 4 me_keepalive_jitter_secs = 4
@@ -206,6 +218,10 @@ me_reconnect_backoff_cap_ms = 10000
me_reconnect_fast_retry_count = 10 me_reconnect_fast_retry_count = 10
hardswap = true hardswap = true
me_reinit_every_secs = 600 me_reinit_every_secs = 600
me_reinit_singleflight = true
me_reinit_max_concurrency = 2
me_reinit_trigger_channel = 64
me_reinit_coalesce_window_ms = 200
me_hardswap_warmup_delay_min_ms = 500 me_hardswap_warmup_delay_min_ms = 500
me_hardswap_warmup_delay_max_ms = 1200 me_hardswap_warmup_delay_max_ms = 1200
me_hardswap_warmup_extra_passes = 2 me_hardswap_warmup_extra_passes = 2
@@ -217,6 +233,8 @@ proxy_secret_rotate_runtime = true
proxy_secret_len_max = 512 proxy_secret_len_max = 512
update_every = 300 update_every = 300
me_pool_drain_ttl_secs = 120 me_pool_drain_ttl_secs = 120
me_bind_stale_mode = "never"
me_bind_stale_ttl_secs = 90
me_pool_min_fresh_ratio = 0.9 me_pool_min_fresh_ratio = 0.9
me_reinit_drain_timeout_secs = 180 me_reinit_drain_timeout_secs = 180
+28 -10
View File
@@ -17,7 +17,7 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
| `general.use_middle_proxy` | `bool` | `true` | none | Enables ME transport mode. If `false`, Direct mode is used. | `use_middle_proxy = true` | | `general.use_middle_proxy` | `bool` | `true` | none | Enables ME transport mode. If `false`, Direct mode is used. | `use_middle_proxy = true` |
| `general.proxy_secret_path` | `Option<String>` | `"proxy-secret"` | path may be `null` | Path to Telegram infrastructure proxy-secret file. | `proxy_secret_path = "proxy-secret"` | | `general.proxy_secret_path` | `Option<String>` | `"proxy-secret"` | path may be `null` | Path to Telegram infrastructure proxy-secret file. | `proxy_secret_path = "proxy-secret"` |
| `general.middle_proxy_nat_ip` | `Option<IpAddr>` | `null` | valid IP when set | Manual public NAT IP override for ME address material. | `middle_proxy_nat_ip = "203.0.113.10"` | | `general.middle_proxy_nat_ip` | `Option<IpAddr>` | `null` | valid IP when set | Manual public NAT IP override for ME address material. | `middle_proxy_nat_ip = "203.0.113.10"` |
| `general.middle_proxy_nat_probe` | `bool` | `true` | auto-forced to `true` when `use_middle_proxy=true` | Enables ME NAT probing. | `middle_proxy_nat_probe = true` | | `general.middle_proxy_nat_probe` | `bool` | `true` | none | Enables ME NAT probing when ME mode and `network.stun_use` are both enabled. | `middle_proxy_nat_probe = true` |
| `general.stun_nat_probe_concurrency` | `usize` | `8` | must be `> 0` | Max parallel STUN probes during NAT discovery. | `stun_nat_probe_concurrency = 16` | | `general.stun_nat_probe_concurrency` | `usize` | `8` | must be `> 0` | Max parallel STUN probes during NAT discovery. | `stun_nat_probe_concurrency = 16` |
| `network.stun_use` | `bool` | `true` | none | Global STUN switch. If `false`, STUN probing is disabled. | `stun_use = true` | | `network.stun_use` | `bool` | `true` | none | Global STUN switch. If `false`, STUN probing is disabled. | `stun_use = true` |
| `network.stun_servers` | `Vec<String>` | built-in public pool | deduplicated + empty values removed | Primary STUN server list for NAT/public endpoint discovery. | `stun_servers = ["stun1.l.google.com:19302"]` | | `network.stun_servers` | `Vec<String>` | built-in public pool | deduplicated + empty values removed | Primary STUN server list for NAT/public endpoint discovery. | `stun_servers = ["stun1.l.google.com:19302"]` |
@@ -31,11 +31,11 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
| Parameter | Type | Default | Constraints / validation | Runtime effect | Example | | Parameter | Type | Default | Constraints / validation | Runtime effect | Example |
|---|---|---:|---|---|---| |---|---|---:|---|---|---|
| `general.middle_proxy_pool_size` | `usize` | `8` | none | Target active ME writer pool size. | `middle_proxy_pool_size = 12` | | `general.middle_proxy_pool_size` | `usize` | `8` | none | Non-enforcing compatibility/startup-log input; active writer targets come from the DC-family floor policy. | `middle_proxy_pool_size = 12` |
| `general.middle_proxy_warm_standby` | `usize` | `16` | none | Reserved compatibility field in current revision (no active runtime consumer). | `middle_proxy_warm_standby = 16` | | `general.middle_proxy_warm_standby` | `usize` | `16` | none | Reserved compatibility field in current revision (no active runtime consumer). | `middle_proxy_warm_standby = 16` |
| `general.me_keepalive_enabled` | `bool` | `true` | none | Enables periodic ME keepalive/ping traffic. | `me_keepalive_enabled = true` | | `general.me_keepalive_enabled` | `bool` | `true` | none | Enables periodic ME keepalive/ping traffic. | `me_keepalive_enabled = true` |
| `general.me_keepalive_interval_secs` | `u64` | `25` | none | Base keepalive interval (seconds). | `me_keepalive_interval_secs = 20` | | `general.me_keepalive_interval_secs` | `u64` | `8` | none | Base keepalive interval (seconds). | `me_keepalive_interval_secs = 20` |
| `general.me_keepalive_jitter_secs` | `u64` | `5` | none | Keepalive jitter to avoid synchronization bursts. | `me_keepalive_jitter_secs = 3` | | `general.me_keepalive_jitter_secs` | `u64` | `2` | none | Keepalive jitter to avoid synchronization bursts. | `me_keepalive_jitter_secs = 3` |
| `general.me_keepalive_payload_random` | `bool` | `true` | none | Randomizes keepalive payload bytes. | `me_keepalive_payload_random = true` | | `general.me_keepalive_payload_random` | `bool` | `true` | none | Randomizes keepalive payload bytes. | `me_keepalive_payload_random = true` |
| `general.me_warmup_stagger_enabled` | `bool` | `true` | none | Staggers extra ME warmup dials to avoid spikes. | `me_warmup_stagger_enabled = true` | | `general.me_warmup_stagger_enabled` | `bool` | `true` | none | Staggers extra ME warmup dials to avoid spikes. | `me_warmup_stagger_enabled = true` |
| `general.me_warmup_step_delay_ms` | `u64` | `500` | none | Base delay between warmup dial steps (ms). | `me_warmup_step_delay_ms = 300` | | `general.me_warmup_step_delay_ms` | `u64` | `500` | none | Base delay between warmup dial steps (ms). | `me_warmup_step_delay_ms = 300` |
@@ -44,6 +44,7 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
| `general.me_reconnect_backoff_base_ms` | `u64` | `500` | none | Initial reconnect backoff (ms). | `me_reconnect_backoff_base_ms = 250` | | `general.me_reconnect_backoff_base_ms` | `u64` | `500` | none | Initial reconnect backoff (ms). | `me_reconnect_backoff_base_ms = 250` |
| `general.me_reconnect_backoff_cap_ms` | `u64` | `30000` | none | Maximum reconnect backoff (ms). | `me_reconnect_backoff_cap_ms = 10000` | | `general.me_reconnect_backoff_cap_ms` | `u64` | `30000` | none | Maximum reconnect backoff (ms). | `me_reconnect_backoff_cap_ms = 10000` |
| `general.me_reconnect_fast_retry_count` | `u32` | `16` | none | Immediate retry budget before long backoff behavior. | `me_reconnect_fast_retry_count = 8` | | `general.me_reconnect_fast_retry_count` | `u32` | `16` | none | Immediate retry budget before long backoff behavior. | `me_reconnect_fast_retry_count = 8` |
| `general.me_writer_byte_budget_bytes` | `usize` | `33570816` | multiple of `16384`; dynamic minimum for `max_client_frame`; maximum `268435456` | Bounded byte permits assigned to each ME writer's outbound staging queue. | `me_writer_byte_budget_bytes = 33570816` |
### 3) Reinit/hardswap, secret rotation, and degradation ### 3) Reinit/hardswap, secret rotation, and degradation
@@ -51,6 +52,10 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
|---|---|---:|---|---|---| |---|---|---:|---|---|---|
| `general.hardswap` | `bool` | `true` | none | Enables generation-based ME hardswap strategy. | `hardswap = true` | | `general.hardswap` | `bool` | `true` | none | Enables generation-based ME hardswap strategy. | `hardswap = true` |
| `general.me_reinit_every_secs` | `u64` | `900` | must be `> 0` | Periodic ME reinit interval. | `me_reinit_every_secs = 600` | | `general.me_reinit_every_secs` | `u64` | `900` | must be `> 0` | Periodic ME reinit interval. | `me_reinit_every_secs = 600` |
| `general.me_reinit_singleflight` | `bool` | `true` | none | Serializes reinit cycles from all trigger sources. | `me_reinit_singleflight = true` |
| `general.me_reinit_max_concurrency` | `usize` | `2` | must be within `[1,8]`; effective value is `1` with singleflight | Bounds concurrent generation warmups; excess triggers coalesce into one rerun. | `me_reinit_max_concurrency = 2` |
| `general.me_reinit_trigger_channel` | `usize` | `64` | must be within `[1,4096]` | Bounds queued reinit trigger notifications in each runtime generation. | `me_reinit_trigger_channel = 64` |
| `general.me_reinit_coalesce_window_ms` | `u64` | `200` | none | Coalesces trigger bursts before one reinit cycle. | `me_reinit_coalesce_window_ms = 200` |
| `general.me_hardswap_warmup_delay_min_ms` | `u64` | `1000` | must be `<= me_hardswap_warmup_delay_max_ms` | Lower bound for hardswap warmup dial spacing. | `me_hardswap_warmup_delay_min_ms = 500` | | `general.me_hardswap_warmup_delay_min_ms` | `u64` | `1000` | must be `<= me_hardswap_warmup_delay_max_ms` | Lower bound for hardswap warmup dial spacing. | `me_hardswap_warmup_delay_min_ms = 500` |
| `general.me_hardswap_warmup_delay_max_ms` | `u64` | `2000` | must be `> 0` | Upper bound for hardswap warmup dial spacing. | `me_hardswap_warmup_delay_max_ms = 1200` | | `general.me_hardswap_warmup_delay_max_ms` | `u64` | `2000` | must be `> 0` | Upper bound for hardswap warmup dial spacing. | `me_hardswap_warmup_delay_max_ms = 1200` |
| `general.me_hardswap_warmup_extra_passes` | `u8` | `3` | must be within `[0,10]` | Additional warmup passes after base pass. | `me_hardswap_warmup_extra_passes = 2` | | `general.me_hardswap_warmup_extra_passes` | `u8` | `3` | must be within `[0,10]` | Additional warmup passes after base pass. | `me_hardswap_warmup_extra_passes = 2` |
@@ -61,12 +66,20 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
| `general.proxy_secret_rotate_runtime` | `bool` | `true` | none | Enables runtime proxy-secret rotation. | `proxy_secret_rotate_runtime = true` | | `general.proxy_secret_rotate_runtime` | `bool` | `true` | none | Enables runtime proxy-secret rotation. | `proxy_secret_rotate_runtime = true` |
| `general.proxy_secret_len_max` | `usize` | `256` | must be within `[32,4096]` | Upper limit for accepted proxy-secret length. | `proxy_secret_len_max = 512` | | `general.proxy_secret_len_max` | `usize` | `256` | must be within `[32,4096]` | Upper limit for accepted proxy-secret length. | `proxy_secret_len_max = 512` |
| `general.update_every` | `Option<u64>` | `300` | if set: must be `> 0`; if `null`: legacy min fallback | Unified refresh interval for ME config + secret updater. | `update_every = 300` | | `general.update_every` | `Option<u64>` | `300` | if set: must be `> 0`; if `null`: legacy min fallback | Unified refresh interval for ME config + secret updater. | `update_every = 300` |
| `general.me_pool_drain_ttl_secs` | `u64` | `90` | none | Time window where stale writers remain fallback-eligible. | `me_pool_drain_ttl_secs = 120` | | `general.me_pool_drain_ttl_secs` | `u64` | `90` | none | Age threshold for prolonged-drain warnings and the lower-bound normalization of force-close timeout; it does not grant stale binds. | `me_pool_drain_ttl_secs = 120` |
| `general.me_pool_min_fresh_ratio` | `f32` | `0.8` | must be within `[0.0,1.0]` | Coverage threshold before stale generation can be drained. | `me_pool_min_fresh_ratio = 0.9` | | `general.me_bind_stale_mode` | `"never"`, `"ttl"`, or `"always"` | `"never"` | none | Controls whether new bindings may use draining stale writers for uncovered DC-family groups. | `me_bind_stale_mode = "never"` |
| `general.me_reinit_drain_timeout_secs` | `u64` | `120` | `0` means no force-close; if `>0 && < TTL` it is bumped to TTL | Force-close timeout for draining stale writers. | `me_reinit_drain_timeout_secs = 0` | | `general.me_bind_stale_ttl_secs` | `u64` | `90` | none | Stale-bind eligibility window used only when `me_bind_stale_mode = "ttl"`; `0` disables TTL expiry for eligible draining writers. | `me_bind_stale_ttl_secs = 90` |
| `general.me_pool_min_fresh_ratio` | `f32` | `0.8` | must be within `[0.0,1.0]` | Minimum fresh DC-family coverage ratio required at commit. | `me_pool_min_fresh_ratio = 0.9` |
| `general.me_reinit_drain_timeout_secs` | `u64` | `90` | `0` uses the 300-second safety fallback; an effective value below the drain TTL is bumped to the TTL | Force-close timeout for draining stale writers. | `me_reinit_drain_timeout_secs = 0` |
| `general.auto_degradation_enabled` | `bool` | `true` | none | Reserved compatibility flag in current revision (no active runtime consumer). | `auto_degradation_enabled = true` | | `general.auto_degradation_enabled` | `bool` | `true` | none | Reserved compatibility flag in current revision (no active runtime consumer). | `auto_degradation_enabled = true` |
| `general.degradation_min_unavailable_dc_groups` | `u8` | `2` | none | Reserved compatibility threshold in current revision (no active runtime consumer). | `degradation_min_unavailable_dc_groups = 2` | | `general.degradation_min_unavailable_dc_groups` | `u8` | `2` | none | Reserved compatibility threshold in current revision (no active runtime consumer). | `degradation_min_unavailable_dc_groups = 2` |
A hardswap candidate is authoritative only for the same desired-map hash and endpoint revision. Repeated attempts reuse that pending generation for at most 1800 seconds; after the pending TTL a fresh generation is allocated. Commit revalidates authority and requires fresh DC-family coverage of at least `me_pool_min_fresh_ratio`. Missing groups block commit when `me_bind_stale_mode = "never"`; `ttl` or `always` may commit with policy-bounded stale fallback for those groups. Covered old writers may retire immediately, so hardswap is an atomic policy transition rather than a universal zero-drop guarantee.
Writer replacement uses a separate cancellation-safe `Open -> Preparing -> Retiring` state. `Preparing` prevents duplicate replacement work but still allows new binds. Under the registry binding guard, commit revalidates the victim, moves it to `Retiring` to block new binds, installs and publishes the successor, and starts draining the predecessor before releasing the guard. Dropping a reservation before that commit boundary restores `Open`.
Operators can observe pending age, writer count and deficit, missing DC groups, map currency, orphan warm writers, and replacement `preparing`/`retiring` phases through `/v1/runtime/me_pool_state`. The corresponding `telemt_me_hardswap_*` and `telemt_me_writer_replacement_current` gauges render zero when ME telemetry is `silent` or the active ME snapshot is unavailable. In Prometheus, pair `telemt_me_hardswap_pending_map_current` with `telemt_me_hardswap_pending`: zero map currency also represents no pending generation, while the API uses `null` for that case. Alert on a pending age approaching 1800 seconds, a persistent writer deficit or missing group count, a stale map, orphan warm writers, or replacement phases that do not converge.
## Deprecated / Legacy Parameters ## Deprecated / Legacy Parameters
| Parameter | Status | Replacement | Current behavior | Migration recommendation | | Parameter | Status | Replacement | Current behavior | Migration recommendation |
@@ -113,8 +126,8 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
- `bind_addresses` candidates (same IP family as target) first; - `bind_addresses` candidates (same IP family as target) first;
- if `interface` is an interface name and `bind_addresses` is set, each candidate IP is validated against addresses currently assigned to that interface; - if `interface` is an interface name and `bind_addresses` is set, each candidate IP is validated against addresses currently assigned to that interface;
- invalid candidates are dropped with `WARN`; - invalid candidates are dropped with `WARN`;
- if no valid candidate remains, connection falls back to unbound direct connect (`bind_ip=None`); - if a non-empty `bind_addresses` list leaves no valid same-family candidate, the connection fails closed with a configuration error;
- if no `bind_addresses` candidate, `interface` is used (literal IP or resolved interface primary IP). - only when `bind_addresses` is absent or empty, `interface` is used (literal IP or resolved interface primary IP); if that yields no address, direct connect remains unbound.
6. For `socks4/socks5` with `address` as hostname, interface binding is not supported and is ignored with warning. 6. For `socks4/socks5` with `address` as hostname, interface binding is not supported and is ignored with warning.
7. Runtime DNS overrides are used for upstream hostname resolution. 7. Runtime DNS overrides are used for upstream hostname resolution.
8. In ME mode, the selected upstream is also used for ME TCP dial path. 8. In ME mode, the selected upstream is also used for ME TCP dial path.
@@ -196,7 +209,6 @@ use_middle_proxy = true
proxy_secret_path = "proxy-secret" proxy_secret_path = "proxy-secret"
middle_proxy_nat_probe = true middle_proxy_nat_probe = true
stun_nat_probe_concurrency = 16 stun_nat_probe_concurrency = 16
middle_proxy_pool_size = 12
me_keepalive_enabled = true me_keepalive_enabled = true
me_keepalive_interval_secs = 20 me_keepalive_interval_secs = 20
me_keepalive_jitter_secs = 4 me_keepalive_jitter_secs = 4
@@ -206,6 +218,10 @@ me_reconnect_backoff_cap_ms = 10000
me_reconnect_fast_retry_count = 10 me_reconnect_fast_retry_count = 10
hardswap = true hardswap = true
me_reinit_every_secs = 600 me_reinit_every_secs = 600
me_reinit_singleflight = true
me_reinit_max_concurrency = 2
me_reinit_trigger_channel = 64
me_reinit_coalesce_window_ms = 200
me_hardswap_warmup_delay_min_ms = 500 me_hardswap_warmup_delay_min_ms = 500
me_hardswap_warmup_delay_max_ms = 1200 me_hardswap_warmup_delay_max_ms = 1200
me_hardswap_warmup_extra_passes = 2 me_hardswap_warmup_extra_passes = 2
@@ -217,6 +233,8 @@ proxy_secret_rotate_runtime = true
proxy_secret_len_max = 512 proxy_secret_len_max = 512
update_every = 300 update_every = 300
me_pool_drain_ttl_secs = 120 me_pool_drain_ttl_secs = 120
me_bind_stale_mode = "never"
me_bind_stale_ttl_secs = 90
me_pool_min_fresh_ratio = 0.9 me_pool_min_fresh_ratio = 0.9
me_reinit_drain_timeout_secs = 180 me_reinit_drain_timeout_secs = 180
+28 -10
View File
@@ -17,7 +17,7 @@
| `general.use_middle_proxy` | `bool` | `true` | нет | Включает транспорт ME. При `false` используется Direct-режим. | `use_middle_proxy = true` | | `general.use_middle_proxy` | `bool` | `true` | нет | Включает транспорт ME. При `false` используется Direct-режим. | `use_middle_proxy = true` |
| `general.proxy_secret_path` | `Option<String>` | `"proxy-secret"` | путь может быть `null` | Путь к инфраструктурному proxy-secret Telegram. | `proxy_secret_path = "proxy-secret"` | | `general.proxy_secret_path` | `Option<String>` | `"proxy-secret"` | путь может быть `null` | Путь к инфраструктурному proxy-secret Telegram. | `proxy_secret_path = "proxy-secret"` |
| `general.middle_proxy_nat_ip` | `Option<IpAddr>` | `null` | валидный IP при задании | Ручной override публичного NAT IP для адресного материала ME. | `middle_proxy_nat_ip = "203.0.113.10"` | | `general.middle_proxy_nat_ip` | `Option<IpAddr>` | `null` | валидный IP при задании | Ручной override публичного NAT IP для адресного материала ME. | `middle_proxy_nat_ip = "203.0.113.10"` |
| `general.middle_proxy_nat_probe` | `bool` | `true` | авто-принудительно `true`, если `use_middle_proxy=true` | Включает NAT probing для ME. | `middle_proxy_nat_probe = true` | | `general.middle_proxy_nat_probe` | `bool` | `true` | нет | Включает NAT probing, когда одновременно включены ME-режим и `network.stun_use`. | `middle_proxy_nat_probe = true` |
| `general.stun_nat_probe_concurrency` | `usize` | `8` | должно быть `> 0` | Максимум параллельных STUN-проб при NAT-детекте. | `stun_nat_probe_concurrency = 16` | | `general.stun_nat_probe_concurrency` | `usize` | `8` | должно быть `> 0` | Максимум параллельных STUN-проб при NAT-детекте. | `stun_nat_probe_concurrency = 16` |
| `network.stun_use` | `bool` | `true` | нет | Глобальный переключатель STUN. При `false` STUN отключен. | `stun_use = true` | | `network.stun_use` | `bool` | `true` | нет | Глобальный переключатель STUN. При `false` STUN отключен. | `stun_use = true` |
| `network.stun_servers` | `Vec<String>` | встроенный публичный пул | удаляются дубликаты и пустые значения | Основной список STUN-серверов для NAT/public endpoint discovery. | `stun_servers = ["stun1.l.google.com:19302"]` | | `network.stun_servers` | `Vec<String>` | встроенный публичный пул | удаляются дубликаты и пустые значения | Основной список STUN-серверов для NAT/public endpoint discovery. | `stun_servers = ["stun1.l.google.com:19302"]` |
@@ -31,11 +31,11 @@
| Параметр | Тип | Default | Ограничения / валидация | Влияние на runtime | Пример | | Параметр | Тип | Default | Ограничения / валидация | Влияние на runtime | Пример |
|---|---|---:|---|---|---| |---|---|---:|---|---|---|
| `general.middle_proxy_pool_size` | `usize` | `8` | нет | Целевой размер активного пула ME-writer соединений. | `middle_proxy_pool_size = 12` | | `general.middle_proxy_pool_size` | `usize` | `8` | нет | Не влияющий на enforcement compatibility/startup-log input; active writer targets определяет DC-family floor policy. | `middle_proxy_pool_size = 12` |
| `general.middle_proxy_warm_standby` | `usize` | `16` | нет | Зарезервированное поле совместимости в текущей ревизии (активного runtime-consumer нет). | `middle_proxy_warm_standby = 16` | | `general.middle_proxy_warm_standby` | `usize` | `16` | нет | Зарезервированное поле совместимости в текущей ревизии (активного runtime-consumer нет). | `middle_proxy_warm_standby = 16` |
| `general.me_keepalive_enabled` | `bool` | `true` | нет | Включает периодические keepalive/ping кадры ME. | `me_keepalive_enabled = true` | | `general.me_keepalive_enabled` | `bool` | `true` | нет | Включает периодические keepalive/ping кадры ME. | `me_keepalive_enabled = true` |
| `general.me_keepalive_interval_secs` | `u64` | `25` | нет | Базовый интервал keepalive (сек). | `me_keepalive_interval_secs = 20` | | `general.me_keepalive_interval_secs` | `u64` | `8` | нет | Базовый интервал keepalive (сек). | `me_keepalive_interval_secs = 20` |
| `general.me_keepalive_jitter_secs` | `u64` | `5` | нет | Джиттер keepalive для предотвращения синхронных всплесков. | `me_keepalive_jitter_secs = 3` | | `general.me_keepalive_jitter_secs` | `u64` | `2` | нет | Джиттер keepalive для предотвращения синхронных всплесков. | `me_keepalive_jitter_secs = 3` |
| `general.me_keepalive_payload_random` | `bool` | `true` | нет | Рандомизирует payload keepalive-кадров. | `me_keepalive_payload_random = true` | | `general.me_keepalive_payload_random` | `bool` | `true` | нет | Рандомизирует payload keepalive-кадров. | `me_keepalive_payload_random = true` |
| `general.me_warmup_stagger_enabled` | `bool` | `true` | нет | Включает staggered warmup дополнительных ME-коннектов. | `me_warmup_stagger_enabled = true` | | `general.me_warmup_stagger_enabled` | `bool` | `true` | нет | Включает staggered warmup дополнительных ME-коннектов. | `me_warmup_stagger_enabled = true` |
| `general.me_warmup_step_delay_ms` | `u64` | `500` | нет | Базовая задержка между шагами warmup (мс). | `me_warmup_step_delay_ms = 300` | | `general.me_warmup_step_delay_ms` | `u64` | `500` | нет | Базовая задержка между шагами warmup (мс). | `me_warmup_step_delay_ms = 300` |
@@ -44,6 +44,7 @@
| `general.me_reconnect_backoff_base_ms` | `u64` | `500` | нет | Начальный backoff reconnect (мс). | `me_reconnect_backoff_base_ms = 250` | | `general.me_reconnect_backoff_base_ms` | `u64` | `500` | нет | Начальный backoff reconnect (мс). | `me_reconnect_backoff_base_ms = 250` |
| `general.me_reconnect_backoff_cap_ms` | `u64` | `30000` | нет | Верхняя граница backoff reconnect (мс). | `me_reconnect_backoff_cap_ms = 10000` | | `general.me_reconnect_backoff_cap_ms` | `u64` | `30000` | нет | Верхняя граница backoff reconnect (мс). | `me_reconnect_backoff_cap_ms = 10000` |
| `general.me_reconnect_fast_retry_count` | `u32` | `16` | нет | Бюджет быстрых retry до длинного backoff. | `me_reconnect_fast_retry_count = 8` | | `general.me_reconnect_fast_retry_count` | `u32` | `16` | нет | Бюджет быстрых retry до длинного backoff. | `me_reconnect_fast_retry_count = 8` |
| `general.me_writer_byte_budget_bytes` | `usize` | `33570816` | кратно `16384`; динамический минимум для `max_client_frame`; максимум `268435456` | Ограниченные byte permits для исходящей staging queue каждого ME writer. | `me_writer_byte_budget_bytes = 33570816` |
### 3) Reinit/hardswap, ротация секрета и деградация ### 3) Reinit/hardswap, ротация секрета и деградация
@@ -51,6 +52,10 @@
|---|---|---:|---|---|---| |---|---|---:|---|---|---|
| `general.hardswap` | `bool` | `true` | нет | Включает generation-based стратегию hardswap для ME-пула. | `hardswap = true` | | `general.hardswap` | `bool` | `true` | нет | Включает generation-based стратегию hardswap для ME-пула. | `hardswap = true` |
| `general.me_reinit_every_secs` | `u64` | `900` | должно быть `> 0` | Интервал периодического reinit ME-пула. | `me_reinit_every_secs = 600` | | `general.me_reinit_every_secs` | `u64` | `900` | должно быть `> 0` | Интервал периодического reinit ME-пула. | `me_reinit_every_secs = 600` |
| `general.me_reinit_singleflight` | `bool` | `true` | нет | Сериализует reinit cycles из всех trigger sources. | `me_reinit_singleflight = true` |
| `general.me_reinit_max_concurrency` | `usize` | `2` | диапазон `[1,8]`; эффективное значение `1` при singleflight | Ограничивает параллельные generation warmup; дополнительные triggers объединяются в один rerun. | `me_reinit_max_concurrency = 2` |
| `general.me_reinit_trigger_channel` | `usize` | `64` | диапазон `[1,4096]` | Ограничивает очередь reinit triggers в каждом runtime generation. | `me_reinit_trigger_channel = 64` |
| `general.me_reinit_coalesce_window_ms` | `u64` | `200` | нет | Объединяет burst triggers перед одним reinit cycle. | `me_reinit_coalesce_window_ms = 200` |
| `general.me_hardswap_warmup_delay_min_ms` | `u64` | `1000` | должно быть `<= me_hardswap_warmup_delay_max_ms` | Нижняя граница пауз между warmup dial попытками. | `me_hardswap_warmup_delay_min_ms = 500` | | `general.me_hardswap_warmup_delay_min_ms` | `u64` | `1000` | должно быть `<= me_hardswap_warmup_delay_max_ms` | Нижняя граница пауз между warmup dial попытками. | `me_hardswap_warmup_delay_min_ms = 500` |
| `general.me_hardswap_warmup_delay_max_ms` | `u64` | `2000` | должно быть `> 0` | Верхняя граница пауз между warmup dial попытками. | `me_hardswap_warmup_delay_max_ms = 1200` | | `general.me_hardswap_warmup_delay_max_ms` | `u64` | `2000` | должно быть `> 0` | Верхняя граница пауз между warmup dial попытками. | `me_hardswap_warmup_delay_max_ms = 1200` |
| `general.me_hardswap_warmup_extra_passes` | `u8` | `3` | диапазон `[0,10]` | Дополнительные warmup-проходы после базового. | `me_hardswap_warmup_extra_passes = 2` | | `general.me_hardswap_warmup_extra_passes` | `u8` | `3` | диапазон `[0,10]` | Дополнительные warmup-проходы после базового. | `me_hardswap_warmup_extra_passes = 2` |
@@ -61,12 +66,20 @@
| `general.proxy_secret_rotate_runtime` | `bool` | `true` | нет | Включает runtime-ротацию proxy-secret. | `proxy_secret_rotate_runtime = true` | | `general.proxy_secret_rotate_runtime` | `bool` | `true` | нет | Включает runtime-ротацию proxy-secret. | `proxy_secret_rotate_runtime = true` |
| `general.proxy_secret_len_max` | `usize` | `256` | диапазон `[32,4096]` | Верхний лимит длины принимаемого proxy-secret. | `proxy_secret_len_max = 512` | | `general.proxy_secret_len_max` | `usize` | `256` | диапазон `[32,4096]` | Верхний лимит длины принимаемого proxy-secret. | `proxy_secret_len_max = 512` |
| `general.update_every` | `Option<u64>` | `300` | если задано: `> 0`; если `null`: fallback на legacy минимум | Единый интервал refresh для ME config + secret updater. | `update_every = 300` | | `general.update_every` | `Option<u64>` | `300` | если задано: `> 0`; если `null`: fallback на legacy минимум | Единый интервал refresh для ME config + secret updater. | `update_every = 300` |
| `general.me_pool_drain_ttl_secs` | `u64` | `90` | нет | Время, когда stale writer ещё может использоваться как fallback. | `me_pool_drain_ttl_secs = 120` | | `general.me_pool_drain_ttl_secs` | `u64` | `90` | нет | Возрастной порог предупреждений о долгом drain и нижняя граница нормализации force-close timeout; stale binds он не разрешает. | `me_pool_drain_ttl_secs = 120` |
| `general.me_pool_min_fresh_ratio` | `f32` | `0.8` | диапазон `[0.0,1.0]` | Порог покрытия fresh-поколения перед drain старого поколения. | `me_pool_min_fresh_ratio = 0.9` | | `general.me_bind_stale_mode` | `"never"`, `"ttl"` или `"always"` | `"never"` | нет | Управляет новыми bindings на draining stale writers для непокрытых DC-family groups. | `me_bind_stale_mode = "never"` |
| `general.me_reinit_drain_timeout_secs` | `u64` | `120` | `0` = без force-close; если `>0 && < TTL`, поднимается до TTL | Таймаут force-close для draining stale writer. | `me_reinit_drain_timeout_secs = 0` | | `general.me_bind_stale_ttl_secs` | `u64` | `90` | нет | Окно stale-bind только для `me_bind_stale_mode = "ttl"`; `0` отключает TTL expiry для разрешённых draining writers. | `me_bind_stale_ttl_secs = 90` |
| `general.me_pool_min_fresh_ratio` | `f32` | `0.8` | диапазон `[0.0,1.0]` | Минимальная доля fresh DC-family coverage при commit. | `me_pool_min_fresh_ratio = 0.9` |
| `general.me_reinit_drain_timeout_secs` | `u64` | `90` | `0` использует safety fallback 300 секунд; effective value ниже drain TTL повышается до TTL | Таймаут force-close для draining stale writer. | `me_reinit_drain_timeout_secs = 0` |
| `general.auto_degradation_enabled` | `bool` | `true` | нет | Зарезервированный флаг совместимости в текущей ревизии (активного runtime-consumer нет). | `auto_degradation_enabled = true` | | `general.auto_degradation_enabled` | `bool` | `true` | нет | Зарезервированный флаг совместимости в текущей ревизии (активного runtime-consumer нет). | `auto_degradation_enabled = true` |
| `general.degradation_min_unavailable_dc_groups` | `u8` | `2` | нет | Зарезервированный порог совместимости в текущей ревизии (активного runtime-consumer нет). | `degradation_min_unavailable_dc_groups = 2` | | `general.degradation_min_unavailable_dc_groups` | `u8` | `2` | нет | Зарезервированный порог совместимости в текущей ревизии (активного runtime-consumer нет). | `degradation_min_unavailable_dc_groups = 2` |
Candidate generation hardswap авторитетна только для того же desired-map hash и endpoint revision. Повторные попытки переиспользуют pending generation не более 1800 секунд; после TTL создаётся новая generation. Commit повторно проверяет authority и требует fresh DC-family coverage не ниже `me_pool_min_fresh_ratio`. Отсутствующие groups блокируют commit при `me_bind_stale_mode = "never"`; `ttl` или `always` позволяют commit с ограниченным policy stale fallback для этих groups. Покрытые старые writers могут сразу перейти в retirement, поэтому hardswap является атомарной сменой policy, а не универсальной zero-drop гарантией.
Writer replacement использует отдельное cancellation-safe состояние `Open -> Preparing -> Retiring`. `Preparing` запрещает дублирующую replacement work, но разрешает новые binds. Под registry binding guard commit повторно проверяет victim, переводит его в `Retiring`, чтобы запретить новые binds, устанавливает и публикует successor и начинает drain predecessor до освобождения guard. Отмена reservation до этой commit boundary возвращает `Open`.
Возраст pending generation, число и дефицит writers, отсутствующие DC groups, актуальность map, orphan warm writers и фазы `preparing`/`retiring` доступны через `/v1/runtime/me_pool_state`. Соответствующие gauges `telemt_me_hardswap_*` и `telemt_me_writer_replacement_current` равны нулю, когда ME telemetry имеет уровень `silent` или активный ME snapshot недоступен. В Prometheus `telemt_me_hardswap_pending_map_current` следует проверять вместе с `telemt_me_hardswap_pending`: нулевая map currency также означает отсутствие pending generation, тогда как API возвращает `null`. Настройте alerts на pending age около 1800 секунд, устойчивый deficit или missing groups, stale map, orphan warm writers и не сходящиеся replacement phases.
## Устаревшие / legacy параметры ## Устаревшие / legacy параметры
| Параметр | Статус | Замена | Текущее поведение | Рекомендация миграции | | Параметр | Статус | Замена | Текущее поведение | Рекомендация миграции |
@@ -113,8 +126,8 @@
- сначала `bind_addresses` (только IP нужного семейства); - сначала `bind_addresses` (только IP нужного семейства);
- если одновременно заданы `interface` (имя) и `bind_addresses`, каждый IP проверяется на принадлежность интерфейсу; - если одновременно заданы `interface` (имя) и `bind_addresses`, каждый IP проверяется на принадлежность интерфейсу;
- несовпадающие IP отбрасываются с `WARN`; - несовпадающие IP отбрасываются с `WARN`;
- если валидных IP не осталось, используется unbound direct connect (`bind_ip=None`); - если непустой список `bind_addresses` не оставляет валидного candidate нужного семейства, connection завершается fail-closed с configuration error;
- если `bind_addresses` не подходит, применяется `interface` (literal IP или адрес интерфейса). - только при отсутствующем или пустом `bind_addresses` применяется `interface` (literal IP или адрес интерфейса); если он также не даёт адрес, direct connect остаётся unbound.
6. Для `socks4/socks5` с `address` в виде hostname интерфейсный bind не поддерживается и игнорируется с предупреждением. 6. Для `socks4/socks5` с `address` в виде hostname интерфейсный bind не поддерживается и игнорируется с предупреждением.
7. Runtime DNS overrides применяются к резолвингу hostname в upstream-подключениях. 7. Runtime DNS overrides применяются к резолвингу hostname в upstream-подключениях.
8. В ME-режиме выбранный upstream также используется для ME TCP dial path. 8. В ME-режиме выбранный upstream также используется для ME TCP dial path.
@@ -196,7 +209,6 @@ use_middle_proxy = true
proxy_secret_path = "proxy-secret" proxy_secret_path = "proxy-secret"
middle_proxy_nat_probe = true middle_proxy_nat_probe = true
stun_nat_probe_concurrency = 16 stun_nat_probe_concurrency = 16
middle_proxy_pool_size = 12
me_keepalive_enabled = true me_keepalive_enabled = true
me_keepalive_interval_secs = 20 me_keepalive_interval_secs = 20
me_keepalive_jitter_secs = 4 me_keepalive_jitter_secs = 4
@@ -206,6 +218,10 @@ me_reconnect_backoff_cap_ms = 10000
me_reconnect_fast_retry_count = 10 me_reconnect_fast_retry_count = 10
hardswap = true hardswap = true
me_reinit_every_secs = 600 me_reinit_every_secs = 600
me_reinit_singleflight = true
me_reinit_max_concurrency = 2
me_reinit_trigger_channel = 64
me_reinit_coalesce_window_ms = 200
me_hardswap_warmup_delay_min_ms = 500 me_hardswap_warmup_delay_min_ms = 500
me_hardswap_warmup_delay_max_ms = 1200 me_hardswap_warmup_delay_max_ms = 1200
me_hardswap_warmup_extra_passes = 2 me_hardswap_warmup_extra_passes = 2
@@ -217,6 +233,8 @@ proxy_secret_rotate_runtime = true
proxy_secret_len_max = 512 proxy_secret_len_max = 512
update_every = 300 update_every = 300
me_pool_drain_ttl_secs = 120 me_pool_drain_ttl_secs = 120
me_bind_stale_mode = "never"
me_bind_stale_ttl_secs = 90
me_pool_min_fresh_ratio = 0.9 me_pool_min_fresh_ratio = 0.9
me_reinit_drain_timeout_secs = 180 me_reinit_drain_timeout_secs = 180
+106 -82
View File
@@ -40,7 +40,7 @@ Runtime validation for API config:
| Content type | `application/json; charset=utf-8` | | Content type | `application/json; charset=utf-8` |
| Prefix | `/v1` | | Prefix | `/v1` |
| Optimistic concurrency | `If-Match: <revision>` on mutating requests (optional) | | Optimistic concurrency | `If-Match: <revision>` on mutating requests (optional) |
| Revision format | SHA-256 hex of current `config.toml` content | | Revision format | SHA-256 hex of the canonical recursive source manifest: normalized source paths plus each source's raw bytes. Formatting, comments, and path changes therefore change the revision. |
### Success Envelope ### Success Envelope
```json ```json
@@ -175,7 +175,7 @@ Notes:
| `POST /v1/users` | Creates a user and returns the effective user view plus secret. | | `POST /v1/users` | Creates a user and returns the effective user view plus secret. |
| `GET /v1/users/{username}` | Returns one disk-first user view or `404` when absent. | | `GET /v1/users/{username}` | Returns one disk-first user view or `404` when absent. |
| `PATCH /v1/users/{username}` | Updates selected per-user fields with JSON Merge Patch semantics. | | `PATCH /v1/users/{username}` | Updates selected per-user fields with JSON Merge Patch semantics. |
| `DELETE /v1/users/{username}` | Deletes one user and related per-user access-map entries. | | `DELETE /v1/users/{username}` | Deletes one user and related API-managed per-user access-map entries. It does not modify `access.user_source_deny`. |
| `POST /v1/users/{username}/rotate-secret` | Rotates one user's secret and returns the effective secret. | | `POST /v1/users/{username}/rotate-secret` | Rotates one user's secret and returns the effective secret. |
| `POST /v1/users/{username}/enable` | Enables one user, removing any disabled override from config. | | `POST /v1/users/{username}/enable` | Enables one user, removing any disabled override from config. |
| `POST /v1/users/{username}/disable` | Disables one user and closes active runtime sessions for that user. | | `POST /v1/users/{username}/disable` | Disables one user and closes active runtime sessions for that user. |
@@ -194,7 +194,7 @@ Notes:
| `403` | `read_only` | Mutating endpoint called while `read_only=true`. | | `403` | `read_only` | Mutating endpoint called while `read_only=true`. |
| `404` | `not_found` | Unknown route, unknown user, or unsupported sub-route. | | `404` | `not_found` | Unknown route, unknown user, or unsupported sub-route. |
| `405` | `method_not_allowed` | Unsupported method for `/v1/users/{username}` route shape. | | `405` | `method_not_allowed` | Unsupported method for `/v1/users/{username}` route shape. |
| `409` | `revision_conflict` | `If-Match` revision mismatch. | | `409` | `revision_conflict` | `If-Match` mismatch, or the source graph/owner changed during a fenced write. |
| `409` | `reload_in_progress` | Another reload operation is non-terminal. | | `409` | `reload_in_progress` | Another reload operation is non-terminal. |
| `409` | `web_runtime_mismatch` | A runtime instance, session reference, or operation reference belongs to another WEB process instance. | | `409` | `web_runtime_mismatch` | A runtime instance, session reference, or operation reference belongs to another WEB process instance. |
| `409` | `web_issuance_enabled` | A WEB close-all operation was requested while effective issuance remained enabled. | | `409` | `web_issuance_enabled` | A WEB close-all operation was requested while effective issuance remained enabled. |
@@ -264,14 +264,14 @@ Notes:
| Field | Type | Required | Description | | Field | Type | Required | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `secret` | `string` | no | Exactly 32 hex chars. | | `secret` | `string` | no | Exactly 32 hex chars. |
| `user_ad_tag` | `string|null` | no | Exactly 32 hex chars; `null` removes the per-user ad tag. | | `user_ad_tag` | `string` or `null` | no | Exactly 32 hex chars; `null` removes the per-user ad tag. |
| `max_tcp_conns` | `usize|null` | no | Per-user concurrent TCP limit; `null` removes the per-user override. | | `max_tcp_conns` | `usize` or `null` | no | Per-user concurrent TCP limit; `null` removes the per-user override. |
| `expiration_rfc3339` | `string|null` | no | RFC3339 expiration timestamp; `null` removes the expiration. | | `expiration_rfc3339` | `string` or `null` | no | RFC3339 expiration timestamp; `null` removes the expiration. |
| `data_quota_bytes` | `u64|null` | no | Per-user traffic quota; `null` removes the per-user quota. | | `data_quota_bytes` | `u64` or `null` | no | Per-user traffic quota; `null` removes the per-user quota. |
| `rate_limit_up_bps` | `u64|null` | no | Per-user upload rate limit in bits per second; `null` removes the upload direction limit. | | `rate_limit_up_bps` | `u64` or `null` | no | Per-user upload rate limit in bits per second; `null` removes the upload direction limit. |
| `rate_limit_down_bps` | `u64|null` | no | Per-user download rate limit in bits per second; `null` removes the download direction limit. | | `rate_limit_down_bps` | `u64` or `null` | no | Per-user download rate limit in bits per second; `null` removes the download direction limit. |
| `max_unique_ips` | `usize|null` | no | Per-user unique source IP limit; `null` removes the per-user override. | | `max_unique_ips` | `usize` or `null` | no | Per-user unique source IP limit; `null` removes the per-user override. |
| `enabled` | `bool|null` | no | `false` disables the user. `true` or `null` removes the disabled override, so the user is enabled. | | `enabled` | `bool` or `null` | no | `false` disables the user. `true` or `null` removes the disabled override, so the user is enabled. |
### `access.user_source_deny` via API ### `access.user_source_deny` via API
- In current API surface, per-user deny-list is **not** exposed as a dedicated field in `CreateUserRequest` / `PatchUserRequest`. - In current API surface, per-user deny-list is **not** exposed as a dedicated field in `CreateUserRequest` / `PatchUserRequest`.
@@ -290,7 +290,7 @@ bob = ["198.51.100.42/32"]
### `PatchConfigRequest` ### `PatchConfigRequest`
A sparse JSON object containing only the top-level config sections to modify. Each key must be one of the editable sections (`general`, `timeouts`, `censorship`, `upstreams`, `dc_overrides`, `web`) or the partially editable `server` object (only `listeners` is allowed under `server`; see below). Tables within a section are deep-merged field-by-field into the existing config; arrays and scalar values replace the existing value wholesale. Untouched sections and file comments are preserved. A sparse JSON object containing only the top-level config sections to modify. Each key must be one of the editable sections (`general`, `timeouts`, `censorship`, `upstreams`, `dc_overrides`, `web`) or the partially editable `server` object (only `listeners` is allowed under `server`; see below). Tables within a section are deep-merged field-by-field into the existing config; arrays and scalar values replace the existing value wholesale. Untouched table bodies and other source files remain byte-identical; a touched TOML table body is reserialized, so comments and formatting inside it can change.
**Rejected keys:** **Rejected keys:**
- `access` → `400 access_not_editable` (users/secrets are managed via `POST/PATCH /v1/users`). - `access` → `400 access_not_editable` (users/secrets are managed via `POST/PATCH /v1/users`).
@@ -323,15 +323,15 @@ Returned by `GET /v1/config` as the envelope `data`. The fields are exactly the
| Field | Type | Description | | Field | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| `general` | `object?` | `[general]` section, if present in config. | | `general` | `object` | Complete normalized `[general]` section, including defaults. |
| `timeouts` | `object?` | `[timeouts]` section, if present. | | `timeouts` | `object` | Complete normalized `[timeouts]` section, including defaults. |
| `censorship` | `object?` | `[censorship]` section, if present. | | `censorship` | `object` | Complete normalized `[censorship]` section, including defaults. |
| `upstreams` | `object?` | `[upstreams]` section, if present. | | `upstreams` | `object[]` | Complete normalized upstream array. When no upstream is authored, the loader inserts one enabled direct upstream. |
| `dc_overrides` | `object?` | `[dc_overrides]` section, if present. | | `dc_overrides` | `object` | Complete normalized DC override map, including the synthesized DC 203 endpoint when it is not authored. |
| `web` | `object?` | Complete authored `[web]` section, if present. The derived runtime-only `web.runtime` field is excluded. | | `web` | `object` | Complete normalized `[web]` section, including defaults. Each `web.vhosts[]` item includes `base_path` (empty string when omitted in TOML). The derived runtime-only `web.runtime` field is excluded. |
| `server` | `object?` | Partial `[server]` view when editable nested fields are present. Currently only `listeners` may appear; `api`/`admin_api`, `port`, unix sockets, and other bind-identity fields are never returned. | | `server` | `object?` | Partial `[server]` view when editable nested fields are present. Currently only `listeners` may appear; `api`/`admin_api`, `port`, unix sockets, and other bind-identity fields are never returned. |
Sections absent from the config file are absent from the response (not `null`). Only the editable sections above are returned; `access` (users/secrets) and `network` (per-node addresses) are always excluded. Under `server`, only the nested field-level allowlist (`listeners`) is exposed. Changes under `[web.limits]` are valid desired configuration but remain process-deferred; the patch response reports `web.limits` in `deferred_process_fields` until restart. The editable typed sections are serialized from the fully defaulted configuration, even when omitted from the source files. Only the editable sections above are returned; `access` (users/secrets) and `network` (per-node addresses) are always excluded. Under `server`, only the nested field-level allowlist (`listeners`) is exposed, and an empty listener array is omitted. Changes under `[web.limits]` are valid desired configuration but remain process-deferred; the patch response reports `web.limits` in `deferred_process_fields` until restart.
### WEB runtime identity and lifecycle ### WEB runtime identity and lifecycle
@@ -463,9 +463,9 @@ Returned by `PATCH /v1/config` on success (`200`, or `202` when a reload was acc
| Field | Type | Description | | Field | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| `revision` | `string` | SHA-256 hex of the config file after the patch was written. | | `revision` | `string` | SHA-256 hex of the canonical recursive source manifest after the patch was written. |
| `restart_required` | `bool` | Legacy classifier result: `true` when the old file watcher alone cannot apply every changed field. Use `runtime_reload_required` and `process_restart_required` for new integrations. | | `restart_required` | `bool` | Legacy classifier result: `true` when the old file watcher alone cannot apply every changed field. Use `runtime_reload_required` and `process_restart_required` for new integrations. |
| `runtime_reload_required` | `bool` | `true` when full effect requires a Maestro runtime-generation reload rather than the legacy hot-field overlay. | | `runtime_reload_required` | `bool` | `true` when effective runtime-owned state differs and must be activated. With a reload query an operation is enqueued; without one, supported hot fields may be applied by the file watcher. |
| `process_restart_required` | `bool` | `true` when a process-owned field changed and remains deferred after an in-process reload. | | `process_restart_required` | `bool` | `true` when a process-owned field changed and remains deferred after an in-process reload. |
| `deferred_process_fields` | `string[]` | Process-owned sockets, paths, capacities, or policies retained by the active process. | | `deferred_process_fields` | `string[]` | Process-owned sockets, paths, capacities, or policies retained by the active process. |
| `changed` | `string[]` | Top-level section names that differed between the old and new config (e.g. `["censorship"]`). | | `changed` | `string[]` | Top-level section names that differed between the old and new config (e.g. `["censorship"]`). |
@@ -495,7 +495,6 @@ Returned by `PATCH /v1/config` on success (`200`, or `202` when a reload was acc
| `connections_bad_total` | `u64` | Failed/invalid client connections. | | `connections_bad_total` | `u64` | Failed/invalid client connections. |
| `connections_bad_by_class` | `ClassCount[]` | Failed/invalid connections grouped by class. | | `connections_bad_by_class` | `ClassCount[]` | Failed/invalid connections grouped by class. |
| `handshake_failures_by_class` | `ClassCount[]` | Handshake failures grouped by class. | | `handshake_failures_by_class` | `ClassCount[]` | Handshake failures grouped by class. |
| `handshake_failures_by_stage` | `StageCount[]` | Handshake failures grouped by state-machine stage. |
| `handshake_timeouts_total` | `u64` | Handshake timeout count. | | `handshake_timeouts_total` | `u64` | Handshake timeout count. |
| `configured_users` | `usize` | Number of configured users in config. | | `configured_users` | `usize` | Number of configured users in config. |
@@ -505,38 +504,6 @@ Returned by `PATCH /v1/config` on success (`200`, or `202` when a reload was acc
| `class` | `string` | Failure class label. | | `class` | `string` | Failure class label. |
| `total` | `u64` | Counter value for this class. | | `total` | `u64` | Counter value for this class. |
#### `StageCount`
| Field | Type | Description |
| --- | --- | --- |
| `stage` | `string` | State-machine stage label. |
| `total` | `u64` | Counter value for this stage. |
#### Handshake failure stage diagnostics
`handshake_failures_by_class` and `telemt_handshake_failures_by_class_total` describe the error kind. `handshake_failures_by_stage` and `telemt_handshake_failures_by_stage_total` describe where the same failure happened in the handshake state machine.
This does not add a DPI verdict or any protocol decision. The stage is derived from the existing Telemt handshake control flow and is counted only when the existing handshake failure or timeout accounting path is reached.
Fixed stage labels:
| Stage | Meaning |
| --- | --- |
| `first_packet_prelude` | Reading the first 5 bytes before selecting the TLS or direct branch. |
| `tls_clienthello_body` | Reading the TLS ClientHello body after the TLS record header. |
| `tls_core` | Running the TLS-F handshake/auth flow. |
| `tls_post_serverhello_mtproto` | Waiting for the 64-byte MTProto handshake after TLS ServerHello. |
| `direct_mtproto` | Reading the direct classic/secure 64-byte MTProto handshake. |
Example:
```text
telemt_handshake_failures_by_class_total{class="expected_64_got_0_unexpected_eof"} 3
telemt_handshake_failures_by_stage_total{stage="direct_mtproto"} 1
telemt_handshake_failures_by_stage_total{stage="tls_post_serverhello_mtproto"} 2
```
This means the same EOF-while-reading-64-bytes failure happened once in the direct MTProto path and twice after TLS ServerHello.
### `SystemInfoData` ### `SystemInfoData`
| Field | Type | Description | | Field | Type | Description |
| --- | --- | --- | | --- | --- | --- |
@@ -550,7 +517,7 @@ This means the same EOF-while-reading-64-bytes failure happened once in the dire
| `process_started_at_epoch_secs` | `u64` | Process start time as Unix epoch seconds. | | `process_started_at_epoch_secs` | `u64` | Process start time as Unix epoch seconds. |
| `uptime_seconds` | `f64` | Process uptime in seconds. | | `uptime_seconds` | `f64` | Process uptime in seconds. |
| `config_path` | `string` | Active config file path used by runtime. | | `config_path` | `string` | Active config file path used by runtime. |
| `config_hash` | `string` | SHA-256 hash of current config content (same value as envelope `revision`). | | `config_hash` | `string` | SHA-256 hash of the canonical recursive configuration source manifest (same value as envelope `revision`). |
| `config_reload_count` | `u64` | Number of successfully observed config updates since process start. | | `config_reload_count` | `u64` | Number of successfully observed config updates since process start. |
| `last_config_reload_epoch_secs` | `u64?` | Unix epoch seconds of the latest observed config reload; null/absent before first reload. | | `last_config_reload_epoch_secs` | `u64?` | Unix epoch seconds of the latest observed config reload; null/absent before first reload. |
@@ -730,6 +697,15 @@ This means the same EOF-while-reading-64-bytes failure happened once in the dire
| --- | --- | --- | | --- | --- | --- |
| `enabled` | `bool` | Hardswap feature toggle. | | `enabled` | `bool` | Hardswap feature toggle. |
| `pending` | `bool` | `true` when pending generation is non-zero. | | `pending` | `bool` | `true` when pending generation is non-zero. |
| `pending_writers_current` | `usize` | Authoritative warm writers owned by the pending generation. |
| `pending_writer_deficit` | `usize` | Writers still required to satisfy the pending generation floor. |
| `pending_missing_dc_groups` | `usize` | Desired DC-family groups below the pending generation floor. |
| `pending_map_current` | `bool?` | Whether the pending generation targets the current endpoint map; serialized as `null` when no comparison is available. |
| `orphan_warm_writers_current` | `usize` | Warm writers not owned by the pending generation. |
| `replacement_preparing_current` | `usize` | Writer replacements preparing a successor. |
| `replacement_retiring_current` | `usize` | Writer replacements retiring a predecessor. |
With no pending generation, `pending_map_current` is `null`. When it is `false`, the pending writer, deficit, and missing-group coverage fields are intentionally zero rather than computed against stale ownership. `orphan_warm_writers_current` counts non-draining Warm writers not owned by a current-map pending generation, so every Warm writer is orphaned when pending ownership is absent or stale. Replacement counts are independent of pending state.
#### `RuntimeMePoolStateWriterData` #### `RuntimeMePoolStateWriterData`
| Field | Type | Description | | Field | Type | Description |
@@ -950,7 +926,7 @@ This means the same EOF-while-reading-64-bytes failure happened once in the dire
| `kdf` | `RuntimeMeSelftestKdfData` | KDF EWMA health state. | | `kdf` | `RuntimeMeSelftestKdfData` | KDF EWMA health state. |
| `timeskew` | `RuntimeMeSelftestTimeskewData` | Date-header skew health state. | | `timeskew` | `RuntimeMeSelftestTimeskewData` | Date-header skew health state. |
| `ip` | `RuntimeMeSelftestIpData` | Interface IP family classification. | | `ip` | `RuntimeMeSelftestIpData` | Interface IP family classification. |
| `pid` | `RuntimeMeSelftestPidData` | Process PID marker (`one|non-one`). | | `pid` | `RuntimeMeSelftestPidData` | Process PID marker (`one` or `non-one`). |
| `bnd` | `RuntimeMeSelftestBndData` | SOCKS BND.ADDR/BND.PORT health state. | | `bnd` | `RuntimeMeSelftestBndData` | SOCKS BND.ADDR/BND.PORT health state. |
#### `RuntimeMeSelftestKdfData` #### `RuntimeMeSelftestKdfData`
@@ -1039,7 +1015,7 @@ This means the same EOF-while-reading-64-bytes failure happened once in the dire
| Field | Type | Description | | Field | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| `username` | `string` | Username. | | `username` | `string` | Username. |
| `current_connections` | `u64` | Current live connections for user. | | `current_connections` | `u64` | Authoritative process-scoped live connections for the user across runtime generations. |
| `total_octets` | `u64` | Cumulative (`client->proxy + proxy->client`) octets. | | `total_octets` | `u64` | Cumulative (`client->proxy + proxy->client`) octets. |
#### `RuntimeEdgeConnectionTelemetryData` #### `RuntimeEdgeConnectionTelemetryData`
@@ -1126,18 +1102,21 @@ JA3 follows the Salesforce ClientHello field order. JA4 follows the FoxIO TLS-cl
| `connections_bad_total` | `u64` | Failed/invalid connections. | | `connections_bad_total` | `u64` | Failed/invalid connections. |
| `connections_bad_by_class` | `ClassCount[]` | Failed/invalid connections grouped by class. | | `connections_bad_by_class` | `ClassCount[]` | Failed/invalid connections grouped by class. |
| `handshake_failures_by_class` | `ClassCount[]` | Handshake failures grouped by class. | | `handshake_failures_by_class` | `ClassCount[]` | Handshake failures grouped by class. |
| `handshake_failures_by_stage` | `StageCount[]` | Handshake failures grouped by state-machine stage. |
| `handshake_timeouts_total` | `u64` | Handshake timeouts. | | `handshake_timeouts_total` | `u64` | Handshake timeouts. |
| `accept_permit_timeout_total` | `u64` | Listener admission permit acquisition timeouts. | | `accept_permit_timeout_total` | `u64` | Listener admission permit acquisition timeouts. |
| `configured_users` | `usize` | Configured user count. | | `configured_users` | `usize` | Configured user count. |
| `telemetry_core_enabled` | `bool` | Core telemetry toggle. | | `telemetry_core_enabled` | `bool` | Core telemetry toggle. |
| `telemetry_user_enabled` | `bool` | User telemetry toggle. | | `telemetry_user_enabled` | `bool` | User telemetry toggle. |
| `telemetry_me_level` | `string` | ME telemetry level (`off|normal|verbose`). | | `telemetry_me_level` | `string` | ME telemetry level (`silent`, `normal`, or `debug`). |
| `conntrack_control_enabled` | `bool` | Whether conntrack control is enabled by policy. | | `conntrack_control_enabled` | `bool` | Whether conntrack control is enabled by policy. |
| `conntrack_control_available` | `bool` | Whether conntrack control backend is currently available. | | `conntrack_control_available` | `bool` | Whether conntrack control backend is currently available. |
| `conntrack_pressure_active` | `bool` | Current conntrack pressure flag. | | `conntrack_pressure_active` | `bool` | Current conntrack pressure flag. |
| `conntrack_event_queue_depth` | `u64` | Current conntrack close-event queue depth. | | `conntrack_event_queue_depth` | `u64` | Current conntrack close-event queue depth. |
| `conntrack_rule_apply_ok` | `bool` | Last conntrack rule application state. | | `conntrack_rule_apply_ok` | `bool` | Last conntrack rule application state. |
| `conntrack_rule_reconcile_success_total` | `u64` | Successful process-owned firewall reconciliations. |
| `conntrack_rule_reconcile_error_total` | `u64` | Failed process-owned firewall reconciliations. |
| `conntrack_rule_rollback_success_total` | `u64` | Successful rollback attempts after partial firewall application. |
| `conntrack_rule_rollback_error_total` | `u64` | Failed rollback attempts after partial firewall application. |
| `conntrack_delete_attempt_total` | `u64` | Conntrack delete attempts. | | `conntrack_delete_attempt_total` | `u64` | Conntrack delete attempts. |
| `conntrack_delete_success_total` | `u64` | Successful conntrack deletes. | | `conntrack_delete_success_total` | `u64` | Successful conntrack deletes. |
| `conntrack_delete_not_found_total` | `u64` | Conntrack delete misses. | | `conntrack_delete_not_found_total` | `u64` | Conntrack delete misses. |
@@ -1222,6 +1201,7 @@ JA3 follows the Salesforce ClientHello field order. JA4 follows the FoxIO TLS-cl
| `reconnect_success_total` | `u64` | Successful reconnects. | | `reconnect_success_total` | `u64` | Successful reconnects. |
| `handshake_reject_total` | `u64` | ME handshake rejects. | | `handshake_reject_total` | `u64` | ME handshake rejects. |
| `handshake_error_codes` | `ZeroCodeCount[]` | Handshake rejects grouped by code. | | `handshake_error_codes` | `ZeroCodeCount[]` | Handshake rejects grouped by code. |
| `handshake_error_code_overflow_total` | `u64` | Handshake rejects whose new error code exceeded the bounded 64-code breakdown. |
| `reader_eof_total` | `u64` | ME reader EOF events. | | `reader_eof_total` | `u64` | ME reader EOF events. |
| `idle_close_by_peer_total` | `u64` | Idle closes initiated by peer. | | `idle_close_by_peer_total` | `u64` | Idle closes initiated by peer. |
| `route_drop_no_conn_total` | `u64` | Route drops due to missing bound connection. | | `route_drop_no_conn_total` | `u64` | Route drops due to missing bound connection. |
@@ -1479,7 +1459,7 @@ JA3 follows the Salesforce ClientHello field order. JA4 follows the FoxIO TLS-cl
| `rate_limit_up_bps` | `u64?` | Optional upload rate limit in bits per second. | | `rate_limit_up_bps` | `u64?` | Optional upload rate limit in bits per second. |
| `rate_limit_down_bps` | `u64?` | Optional download rate limit in bits per second. | | `rate_limit_down_bps` | `u64?` | Optional download rate limit in bits per second. |
| `max_unique_ips` | `usize?` | Optional unique IP limit. | | `max_unique_ips` | `usize?` | Optional unique IP limit. |
| `current_connections` | `u64` | Current live connections. | | `current_connections` | `u64` | Authoritative process-scoped live connections for this user across runtime generations; independent of optional per-user telemetry. |
| `active_unique_ips` | `usize` | Current active unique source IPs. | | `active_unique_ips` | `usize` | Current active unique source IPs. |
| `active_unique_ips_list` | `ip[]` | Current active unique source IP list. | | `active_unique_ips_list` | `ip[]` | Current active unique source IP list. |
| `recent_unique_ips` | `usize` | Unique source IP count inside the configured recent window. | | `recent_unique_ips` | `usize` | Unique source IP count inside the configured recent window. |
@@ -1494,6 +1474,9 @@ JA3 follows the Salesforce ClientHello field order. JA4 follows the FoxIO TLS-cl
| `active_ips` | `ip[]` | Active source IPs for this user. | | `active_ips` | `ip[]` | Active source IPs for this user. |
#### `UserLinks` #### `UserLinks`
`UserLinks` contains only native MTProxy links. It never contains a WEB `tg://webproxy` link; WEB links use a separate startup-only derivation contract based on `web.vhosts[].host`, `base_path`, and profile secret mode.
| Field | Type | Description | | Field | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| `classic` | `string[]` | Active `tg://proxy` links for classic mode. | | `classic` | `string[]` | Active `tg://proxy` links for classic mode. |
@@ -1546,16 +1529,19 @@ Returns the current editable config sections as TOML-shaped JSON, plus the curre
**Auth:** requires `Authorization` header when `auth_header` is configured (same as all other endpoints). **Auth:** requires `Authorization` header when `auth_header` is configured (same as all other endpoints).
**Success `200` response body** (`data` field of the standard envelope): **Abridged success `200` response body:**
```json ```json
{ {
"revision": "<sha256-hex>", "ok": true,
"censorship": {"tls_domain": "front.example.com"}, "data": {
"general": {"log_level": "normal"} "censorship": {"tls_domain": "front.example.com"},
"general": {"log_level": "normal"}
},
"revision": "<sha256-hex>"
} }
``` ```
The response is built from the validated, include-expanded configuration and may therefore contain normalized defaults or synthesized listeners that are absent from the root file. Only `GET` and `PATCH` are accepted; any other method returns `405 Method Not Allowed` with `Allow: GET, PATCH`. The real `data` object contains every fully defaulted editable section; the example omits most fields for readability. The response is built from the validated, include-expanded configuration and may therefore contain normalized defaults or synthesized listeners that are absent from the root file. Only `GET` and `PATCH` are accepted; any other method returns `405 Method Not Allowed` with `Allow: GET, PATCH`.
--- ---
@@ -1571,20 +1557,20 @@ Applies a sparse patch to the editable config sections. The merged config is ful
| --- | --- | --- | | --- | --- | --- |
| `Authorization` | when configured | Same token as all other endpoints. | | `Authorization` | when configured | Same token as all other endpoints. |
| `Content-Type: application/json` | recommended | Not enforced, but body must be valid JSON. | | `Content-Type: application/json` | recommended | Not enforced, but body must be valid JSON. |
| `If-Match: <revision>` | no | Optimistic concurrency. `<revision>` is the `revision` value from `GET /v1/config` or `config_hash` from `GET /v1/system/info`. It covers the complete recursive include graph. If supplied and it does not match the current source manifest, returns `409 revision_conflict`. If omitted, the patch applies unconditionally. | | `If-Match: <revision>` | no | Optimistic concurrency. `<revision>` is the `revision` value from `GET /v1/config` or `config_hash` from `GET /v1/system/info`. It covers the complete recursive include graph. If supplied and it does not match the current source manifest, returns `409 revision_conflict`. Omitting it removes the caller precondition, but the internal graph/owner race fence can still return the same conflict. |
**Editable sections:** `general`, `timeouts`, `censorship`, `upstreams`, `dc_overrides`, plus partially editable `server` (only nested `listeners`). **Editable sections:** `general`, `timeouts`, `censorship`, `upstreams`, `dc_overrides`, `web`, plus partially editable `server` (only nested `listeners`).
**Rejected keys and their error codes:** **Rejected keys and their error codes:**
| Key | HTTP | `error.code` | | Key | HTTP | `error.code` |
| --- | --- | --- | | --- | --- | --- |
| `access` | `400` | `access_not_editable` | | `access` | `400` | `access_not_editable` |
| `network`, `web`, or any unknown top-level key | `400` | `section_not_editable` | | `network` or any unknown top-level key | `400` | `section_not_editable` |
| `server` with keys other than `listeners` | `400` | `field_not_editable` | | `server` with keys other than `listeners` | `400` | `field_not_editable` |
| Object with no editable key | `400` | `bad_request` | | Object with no editable key | `400` | `bad_request` |
**Merge semantics:** tables are deep-merged field-by-field; arrays and scalar values replace the existing value wholesale. A mutation is written to the single source file that owns every touched semantic section. File comments, the root file when it is not the owner, and all other include files are preserved. A target split across sources, a patch spanning multiple owners, or an include directive nested inside a TOML table returns `409 config_patch_not_atomic` without writing any file. **Merge semantics:** tables are deep-merged field-by-field; arrays and scalar values replace the existing value wholesale. In particular, `web.vhosts` is an array: changing one vhost `base_path` requires sending the complete vhost array, including every retained vhost and each required `host`, `public_addr`, `decoy`, and profile field. A mutation is written to the single source file that owns every touched semantic section. Untouched table bodies, the root file when it is not the owner, and all other include files remain byte-identical; touched TOML table bodies are reserialized and may lose their internal formatting or comments. A target split across sources, a patch spanning multiple owners, or an include directive nested inside a TOML table returns `409 config_patch_not_atomic` without writing any file.
**Validation:** the merged config is deserialized into the full `ProxyConfig` type and validated before writing. Failures return `400` with a descriptive message; the file is not modified. **Validation:** the merged config is deserialized into the full `ProxyConfig` type and validated before writing. Failures return `400` with a descriptive message; the file is not modified.
@@ -1623,11 +1609,29 @@ Without a `reload` query parameter, the endpoint writes the patch and the file w
- `revision` — SHA-256 hex of the canonical source manifest after the write, including every recursive include path and its raw bytes. - `revision` — SHA-256 hex of the canonical source manifest after the write, including every recursive include path and its raw bytes.
- `restart_required` — legacy file-watcher classification retained for compatibility. - `restart_required` — legacy file-watcher classification retained for compatibility.
- `runtime_reload_required` — reports whether a full Maestro generation reload is needed for runtime effect. - `runtime_reload_required` — reports that effective runtime-owned state differs and needs activation. With an explicit reload query Telemt enqueues the immutable snapshot; otherwise the watcher may apply supported hot fields.
- `process_restart_required` and `deferred_process_fields` — report process-owned sockets, paths, capacities, or policies that remain unchanged by an in-process reload, including `web.decoy_fasttrack_mode`. A pure listener endpoint move is reloadable only when every retained endpoint keeps identical bind policy and neither the active nor desired listener set uses SYN limiting; same-address MSS, PROXY protocol, backlog, reuse, or SYN-limit changes remain deferred. - `process_restart_required` and `deferred_process_fields` — report process-owned sockets, paths, capacities, or policies that remain unchanged by an in-process reload, including `web.decoy_fasttrack_mode`. A native-listener endpoint-only move is reloadable only when the complete WEB listener plan remains identical, every retained endpoint keeps identical bind policy, and neither the active nor desired listener set uses SYN limiting; same-address MSS, PROXY protocol, backlog, reuse, or SYN-limit changes remain deferred.
- `changed` — list of top-level section names that differed. - `changed` — list of top-level section names that differed.
- `reload` — accepted operation metadata; omitted without a reload query and for process-only patches that cannot change the active generation. - `reload` — accepted operation metadata; omitted without a reload query and for process-only patches that cannot change the active generation.
Example — replace the complete vhost array while changing one `base_path`:
```json
{
"web": {
"vhosts": [{
"host": "proxy.example.com",
"base_path": "telegram/web",
"public_addr": "203.0.113.10:443",
"decoy": {"mode": "http_upstream", "upstream": "http://127.0.0.1:18081"},
"profiles": [{"user": "web-user", "secret_mode": "dd"}]
}]
}
}
```
A valid base-path-only change reports `restart_required=false`, `runtime_reload_required=true`, `process_restart_required=false`, `deferred_process_fields=[]`, and `changed=["web"]`. An invalid path returns `400 bad_request`; no source file or active runtime state changes.
**Status codes:** **Status codes:**
| HTTP | `error.code` | Condition | | HTTP | `error.code` | Condition |
@@ -1641,7 +1645,7 @@ Without a `reload` query parameter, the endpoint writes the patch and the file w
| `401` | `unauthorized` | Missing or invalid `Authorization` header. | | `401` | `unauthorized` | Missing or invalid `Authorization` header. |
| `403` | `read_only` | API is in read-only mode. | | `403` | `read_only` | API is in read-only mode. |
| `405` | `method_not_allowed` | Method other than `GET` or `PATCH` used on `/v1/config`. | | `405` | `method_not_allowed` | Method other than `GET` or `PATCH` used on `/v1/config`. |
| `409` | `revision_conflict` | `If-Match` header supplied but does not match current revision. | | `409` | `revision_conflict` | `If-Match` does not match, or the source graph/owner changes during the fenced write. |
| `409` | `reload_in_progress` | Another runtime reload is active; the patch is not written. | | `409` | `reload_in_progress` | Another runtime reload is active; the patch is not written. |
| `409` | `config_patch_not_atomic` | Touched semantic sections have multiple source owners or cannot be mutated as one source-file transaction. | | `409` | `config_patch_not_atomic` | Touched semantic sections have multiple source owners or cannot be mutated as one source-file transaction. |
| `500` | `internal_error` | I/O or serialization failure. | | `500` | `internal_error` | I/O or serialization failure. |
@@ -1690,20 +1694,23 @@ The API exposes WEB desired configuration through the common config resource, pr
| Operation | Current contract | | Operation | Current contract |
| --- | --- | | --- | --- |
| Read or patch `[web]`, vhosts, profiles, decoys, timeouts, or limits | Supported through `GET` and `PATCH /v1/config`; `web.runtime` is derived and excluded. Tables deep-merge, arrays replace wholesale; `web.limits` and `web.decoy_fasttrack_mode` remain process-deferred. | | Read or patch `[web]`, vhosts, profiles, decoys, timeouts, or limits | Supported through `GET` and `PATCH /v1/config`; `web.runtime` is derived and excluded. Tables deep-merge, arrays replace wholesale; changing one `web.vhosts[].base_path` therefore requires the complete vhost array. `web.limits` and `web.decoy_fasttrack_mode` remain process-deferred. |
| Persist `server.listeners` | Supported through `PATCH /v1/config`. Arrays replace wholesale. A changed WEB listener is process-owned and remains deferred until process restart. | | Persist `server.listeners` | Supported through `PATCH /v1/config`; arrays replace wholesale. A native endpoint-only change may rebind in-process under the constraints above. Any WEB listener-plan change and unsupported native policy change remain deferred until process restart. |
| Apply an externally edited WEB config | Update the owning TOML source, call `POST /v1/system/reload`, then poll `GET /v1/system/reload/{id}`. | | Apply an externally edited WEB config | Update the owning TOML source, call `POST /v1/system/reload`, then poll `GET /v1/system/reload/{id}`. |
| Inspect restart requirements | Read `deferred_process_fields` from reload status. `server.listeners`, `web.limits`, and `web.decoy_fasttrack_mode` require process restart. | | Inspect restart requirements | Read `deferred_process_fields` from reload status. Unsupported `server.listeners` changes, `web.limits`, and `web.decoy_fasttrack_mode` require process restart. |
| Inspect WEB lifecycle, capacity, sessions, operations, learning, and debug state | Use the authenticated `GET /v1/runtime/web/*` routes documented above. | | Inspect WEB lifecycle, capacity, sessions, operations, learning, and debug state | Use the authenticated `GET /v1/runtime/web/*` routes documented above. |
| Pause, drain, or resume new WEB work | Use `POST /v1/runtime/web/lifecycle/pause`, `/drain`, or `/resume` with the current `runtime_instance`. |
| Close selected or all point-in-time sessions | Use `POST /v1/runtime/web/sessions/close`; close-all first requires effective issuance to be disabled. | | Close selected or all point-in-time sessions | Use `POST /v1/runtime/web/sessions/close`; close-all first requires effective issuance to be disabled. |
| Clear debug records or reset carrier learning | Use `POST /v1/runtime/web/debug/clear` or `/carrier-learning/reset` with the current `runtime_instance`. | | Clear debug records or reset carrier learning | Use `POST /v1/runtime/web/debug/clear` or `/carrier-learning/reset` with the current `runtime_instance`. |
| Manage access users | Use `/v1/users`. Creating a user does not add it to `web.vhosts.profiles`; add profile membership through the `web` config patch. | | Manage access users | Use `/v1/users`. Creating a user does not add it to `web.vhosts.profiles`; add profile membership through the `web` config patch. |
| Disable one user | `POST /v1/users/{username}/disable` updates admission immediately and cancels the user's active sessions. | | Disable one user | `POST /v1/users/{username}/disable` updates admission immediately and cancels the user's active sessions. |
| Rotate a profiled user's secret | Use `/v1/users/{username}/rotate-secret`; the config watcher rebuilds WEB capabilities from the new access snapshot. The API returns the secret, not a `tg://webproxy` link. | | Rotate a profiled user's secret | Use `/v1/users/{username}/rotate-secret`; the durable credential identity is staged immediately and active owners for the old identity are cancelled. The config watcher rebuilds WEB capabilities from the new access snapshot. The API returns the raw secret, not a `tg://webproxy` link. |
| Read WEB-specific runtime diagnostics | Use authenticated `GET /web-status`; filters cover client IP, process session ID, User-Agent, and non-secret key fingerprint, with optional grouping, expandable HTTP request-to-response details, and WebSocket handshake/message/frame rows. | | Read WEB-specific runtime diagnostics | Use authenticated `GET /web-status`; filters cover client IP, process session ID, User-Agent, and non-secret key fingerprint, with optional grouping, expandable HTTP request-to-response details, and WebSocket handshake/message/frame rows. |
`web.enabled`, `web.carrier`, `web.debug`, `web.timeouts`, vhosts, profiles, and decoy snapshots are runtime-generation fields. A changed carrier applies only to newly issued bridge sessions; existing sessions and issued bootstrap chains retain their issuance-time policy. `web.enabled=false` stops new issuance but never closes live sessions implicitly. WEB listener inventory and trust policy, plus all `[web.limits]`, are process-owned. A successful reload can therefore activate the runtime-owned subset while reporting the process-owned subset as deferred. `web.enabled`, `web.carrier`, `web.debug`, `web.timeouts`, vhosts, profiles, and decoy snapshots are runtime-generation fields. A changed carrier applies only to newly issued bridge sessions; existing sessions and issued bootstrap chains retain their issuance-time policy. `web.enabled=false` stops new issuance but never closes live sessions implicitly. WEB listener inventory and trust policy, plus all `[web.limits]`, are process-owned. A successful reload can therefore activate the runtime-owned subset while reporting the process-owned subset as deferred.
`base_path` scopes only the WEB data listener. It never prefixes Control API `/v1/*`, `/web-status`, or `/metrics`. `GET /v1/config` shows the desired path but does not prove runtime activation because WEB status intentionally exposes no host, path, or capability. Confirm a terminal reload, the expected `runtime.generation_id`, and external probes of the new and old exact routes. Paths and base paths are never Prometheus labels.
Before deleting a user referenced by a WEB profile, remove and apply the profile first. User mutations validate the complete resulting configuration, so a dangling WEB profile is rejected rather than persisted. Before deleting a user referenced by a WEB profile, remove and apply the profile first. User mutations validate the complete resulting configuration, so a dangling WEB profile is rejected rather than persisted.
The API whitelist is evaluated against the direct TCP peer and does not use the WEB listener's `X-Forwarded-For` policy. `/web-status` inherits API enablement, whitelist, gray action, and exact authorization-header checks; it accepts only `GET`, normalizes a trailing slash, sets `no-store` and restrictive browser security headers, caps each page at 8 MiB, and permits at most two concurrent renderers. Keep the API on a separate loopback or private bind, use a narrow whitelist and a non-empty exact `auth_header`, and do not expose it through the public WEB vhost. The API whitelist is evaluated against the direct TCP peer and does not use the WEB listener's `X-Forwarded-For` policy. `/web-status` inherits API enablement, whitelist, gray action, and exact authorization-header checks; it accepts only `GET`, normalizes a trailing slash, sets `no-store` and restrictive browser security headers, caps each page at 8 MiB, and permits at most two concurrent renderers. Keep the API on a separate loopback or private bind, use a narrow whitelist and a non-empty exact `auth_header`, and do not expose it through the public WEB vhost.
@@ -1717,19 +1724,24 @@ Deployment, TLS-terminator examples, links, and WEB-specific verification are do
| Endpoint | Notes | | Endpoint | Notes |
| --- | --- | | --- | --- |
| `PATCH /v1/config` | Deep-merges and validates the patch, writes touched sections via atomic `tmp + rename`, and optionally submits the exact written revision for an in-process Maestro reload. | | `PATCH /v1/config` | Deep-merges and validates the patch, writes touched sections via atomic `tmp + rename`, and optionally submits the exact written revision for an in-process Maestro reload. |
| `POST /v1/users` | Creates user, validates config, then atomically updates only affected `access.*` TOML tables (`access.users` always, plus optional per-user tables present in request). | | `POST /v1/users` | Creates and validates a user, atomically updates only affected `access.*` TOML tables, then stages the credential and enabled state in process-wide admission after the durable write. |
| `PATCH /v1/users/{username}` | Partial update of provided fields only. Missing fields remain unchanged; explicit `null` removes optional per-user entries. The write path updates only affected `access.*` TOML tables. | | `PATCH /v1/users/{username}` | Partial update of provided fields only. Missing fields remain unchanged; explicit `null` removes optional entries. Admission is staged only when `secret` or `enabled` changes; an identity change or disable cancels current owners, while a metadata-only patch does not. |
| `POST /v1/users/{username}/rotate-secret` | Replaces the user's secret with a provided valid 32-hex value or a generated value, then returns the effective secret in `CreateUserResponse`. | | `POST /v1/users/{username}/rotate-secret` | Replaces the user's secret with a provided valid 32-hex value or a generated value, stages the new process-wide admission identity, cancels owners of the old identity, then returns the effective secret in `CreateUserResponse`. |
| `POST /v1/users/{username}/enable` | Enables the user idempotently by removing the `access.user_enabled[username]` override and updating the runtime admission state immediately. | | `POST /v1/users/{username}/enable` | Enables the user idempotently by removing the `access.user_enabled[username]` override and updating the runtime admission state immediately. |
| `POST /v1/users/{username}/disable` | Disables the user idempotently by writing `access.user_enabled[username] = false`, updating runtime admission immediately, and cancelling active sessions for that username. | | `POST /v1/users/{username}/disable` | Disables the user idempotently by writing `access.user_enabled[username] = false`, updating runtime admission immediately, and cancelling active sessions for that username. |
| `POST /v1/users/{username}/reset-quota` | Resets the runtime quota counter for the route username, persists quota state to `general.quota_state_path`, and does not modify user config. | | `POST /v1/users/{username}/reset-quota` | Resets the runtime quota counter for the route username, persists quota state to `general.quota_state_path`, and does not modify user config. |
| `DELETE /v1/users/{username}` | Deletes only specified user, removes this user from related optional `access.user_*` maps, blocks last-user deletion, and atomically updates only related `access.*` TOML tables. | | `DELETE /v1/users/{username}` | Deletes only the specified user, removes it from API-managed optional `access.user_*` maps, blocks last-user deletion, stages a deletion tombstone that cancels active owners, and atomically updates only related API-managed `access.*` TOML tables. It leaves `access.user_source_deny` untouched; manage that table manually in TOML. |
All mutating endpoints: All accepted durable config, user, and quota mutations:
- Respect `read_only` mode. - Respect `read_only` mode.
- Accept optional `If-Match` for optimistic concurrency. - Accept optional `If-Match` for optimistic concurrency.
- Return new `revision` after successful write. - Return new `revision` after successful write.
- Use process-local mutation lock + atomic write (`tmp + rename`) for config persistence. - Continue to completion after the server has accepted the mutation even if the requesting client disconnects or cancels the HTTP request.
- Serialize through one process-local async mutation lock.
- Publish mandatory process-wide admission state only after the durable write; a stale runtime generation cannot overwrite a newer user mutation.
- Keep the mutation admission override authoritative until the matching active config-source value arrives; publications from older or non-active generations are rejected.
For Unix config and user source writes, Telemt additionally takes an advisory `flock` on the root source's sibling `.lock` file, rechecks the complete source-graph revision and owner contents, and replaces the owning source through a same-directory atomic rename. Every source involved must be a non-symlink regular file with one directory entry, at most 8 MiB, and unchanged while read. The replacement preserves the existing UID, GID, and mode, syncs the temporary file before rename, and attempts to sync the parent directory afterward. Rename is the commit boundary; a later directory-sync failure is logged as a durability warning and does not roll back the already committed mutation. External writers coordinate only if they honor the same sidecar lock. Quota-state persistence does not use this config-source rename path.
Docker deployment note: Docker deployment note:
- Mutating endpoints require `config.toml` to live inside a writable mounted directory. - Mutating endpoints require `config.toml` to live inside a writable mounted directory.
@@ -1777,6 +1789,18 @@ When `general.use_middle_proxy=true` and `general.me2dc_fallback=true`:
`6s` after readiness has been observed at least once (runtime failover timeout). `6s` after readiness has been observed at least once (runtime failover timeout).
- While fallback is active, new sessions are routed via Direct-DC; when ME becomes ready, routing returns to Middle mode. Direct sessions affected by the cutover are closed with the existing staggered delay so clients reconnect through the current route. - While fallback is active, new sessions are routed via Direct-DC; when ME becomes ready, routing returns to Middle mode. Direct sessions affected by the cutover are closed with the existing staggered delay so clients reconnect through the current route.
## Additional Runtime Metrics
The current runtime exports these additional bounded-cardinality families. All use closed labels except the explicitly capped per-user family described below:
- `telemt_me_hardswap_pending`, `telemt_me_hardswap_pending_age_seconds`, `telemt_me_hardswap_pending_writers_current`, `telemt_me_hardswap_pending_writer_deficit`, `telemt_me_hardswap_pending_missing_dc_groups`, `telemt_me_hardswap_pending_map_current`, and `telemt_me_hardswap_orphan_warm_writers_current` describe the authoritative pending generation. They render zero when ME telemetry is `silent` or the active ME snapshot is unavailable. Prometheus `pending_map_current=0` represents both no pending generation and a stale pending map, so pair it with `telemt_me_hardswap_pending`; the API distinguishes no pending generation with `null`.
- `telemt_me_hardswap_pending_reuse_total` is emitted at debug ME telemetry and counts reuse of matching pending ownership; `telemt_me_hardswap_pending_ttl_expired_total` is emitted at normal telemetry and counts 1800-second pending expiry.
- `telemt_me_writer_replacement_current{state="preparing"|"retiring"}` describes transactional writer replacement phases.
- `telemt_conntrack_rule_reconcile_total{result="success"|"error"}` and `telemt_conntrack_rule_rollback_total{result="success"|"error"}` describe process-owned firewall reconcile and best-effort rollback attempts.
- The conntrack reconcile/rollback counters and rate-limiter CAS samples render zero while core telemetry is disabled; conntrack control-state gauges continue to report their effective state.
- `telemt_rate_limiter_cas_retry_exhausted_total{scope,direction,operation}` uses the closed labels `scope=user|cidr`, `direction=up|down`, and `operation=reserve|refund`. Reserve exhaustion returns a zero grant without classifying it as configured throttling; refund exhaustion retains the charge. Neither outcome is a connection-drop counter.
- `telemt_user_connections_current{user}` uses the same authoritative process-scoped admission count as API `current_connections`; it does not reset at a runtime generation boundary. Its Prometheus samples are emitted only when user telemetry is enabled and remain bounded to 4096 tracked telemetry users; `/v1/users` rows and their process-scoped counts are independent of that optional telemetry.
## Serialization Rules ## Serialization Rules
- Success responses always include `revision`. - Success responses always include `revision`.
+24 -12
View File
@@ -57,12 +57,14 @@ Refill works asynchronously and should not block hot routing paths.
`Registry` is the routing index between ME and client sessions: `Registry` is the routing index between ME and client sessions:
- `conn_id -> client response channel` - `conn_id -> client response channel`
- `conn_id <-> writer_id` binding map - `conn_id <-> writer_id` binding map
- writer send routes and their replacement state
- writer activity snapshots and idle tracking - writer activity snapshots and idle tracking
Main invariants: Main invariants:
- A `conn_id` routes to at most one active response channel. - A `conn_id` routes to at most one active response channel.
- Writer loss triggers safe unbind/cleanup and close propagation. - Writer loss triggers safe unbind/cleanup and close propagation.
- Registry state is the source of truth for active ME-bound session mapping. - Registry state is the source of truth for active ME-bound session mapping.
- The registry binding lock linearizes client binds, writer publication, and the transition that closes a replacement victim to new binds.
## Adaptive Floor ## Adaptive Floor
@@ -100,7 +102,9 @@ Goals:
### Transition intent ### Transition intent
- `Warm -> Active`: when coverage/readiness conditions are satisfied. - `Warm -> Active`: when coverage/readiness conditions are satisfied.
- `Active -> Draining`: on generation swap, endpoint replacement, or controlled retirement. - `Active -> Draining`: on generation swap, endpoint replacement, or controlled retirement.
- `Draining -> removed`: after drain TTL/force-close policy (or when naturally empty). - `Draining -> removed`: when naturally empty, at the effective force-close deadline, or through threshold/control-path eviction. `me_pool_drain_ttl_secs` is a warning threshold and force-close lower bound, not a removal deadline by itself.
Writer replacement is a separate registry-local lifecycle: `Open -> Preparing -> Retiring`. `Preparing` excludes duplicate replacement work but intentionally permits new client binds. Commit revalidates the victim under the binding lock; `Retiring` rejects new binds. Dropping an uncommitted reservation restores `Open`, while the writer's contour remains independently `Warm`, `Active`, or `Draining`.
This separation reduces SPOF and keeps cutovers predictable. This separation reduces SPOF and keeps cutovers predictable.
@@ -111,14 +115,17 @@ Generation isolates pool epochs during reinit/reconfiguration.
### Lifecycle phases ### Lifecycle phases
1. `Bootstrap`: initial writers are established. 1. `Bootstrap`: initial writers are established.
2. `Warmup`: next generation writers are created and validated. 2. `Warmup`: next generation writers are created and validated.
3. `Activation`: generation promoted to active when coverage gate passes. 3. `Activation`: generation is promoted atomically when the configured coverage ratio and stale-binding policy pass commit-time revalidation.
4. `Drain`: previous generation becomes draining, existing sessions are allowed to finish. 4. `Drain`: policy-eligible old writers remain as bounded stale fallback; covered old writers become ineligible and enter retirement during the commit, then may close immediately after it returns.
5. `Retire`: old generation writers are removed after graceful rules. 5. `Retire`: draining writers are removed when empty or by force-close, threshold, or explicit control policy.
### Operational guarantees ### Operational guarantees
- No partial generation activation without minimum coverage. - Activation is atomic, but the committed topology may still have missing DC-family groups when `me_pool_min_fresh_ratio` passes and `me_bind_stale_mode` permits bounded stale fallback. Mode `never` rejects any missing group.
- Existing healthy client sessions should not be dropped just because a new generation appears. - Generation handover is policy-bound, not universally zero-drop: covered old writers may be retired and their bound sessions closed immediately after commit, while only selected stale writers remain available for uncovered groups.
- Draining generation exists to absorb in-flight traffic during swap. - A pending generation owns only writers accepted for its generation and current endpoint map; stale tasks cannot publish into a newer generation.
- A pending generation is keyed by desired-map hash and endpoint revision and may be reused for up to 1800 seconds before expiring.
- Writer replacement prepares a successor; under one binding guard, commit first moves the predecessor to `Retiring` and then registers the successor before releasing the guard. Failed or cancelled preparation before that boundary preserves the predecessor and releases the reservation.
- Pool-state telemetry exposes pending writer count and deficit, missing DC-family groups, map currency, orphan warm writers, and replacement `preparing`/`retiring` counts.
### Readiness and admission ### Readiness and admission
Pool readiness is not equivalent to “all endpoints fully saturated”. Pool readiness is not equivalent to “all endpoints fully saturated”.
@@ -149,8 +156,9 @@ Architectural rule:
### Ownership Model ### Ownership Model
Ownership is centered around explicit state domains: Ownership is centered around explicit state domains:
- `MePool` owns writer lifecycle and policy state. - `MePool` owns writer inventory, contour lifecycle, and runtime policy state.
- `Registry` owns per-connection routing bindings. - The reinit coordinator owns active/pending generation authority keyed by map hash and endpoint revision.
- `Registry` owns per-connection routing bindings, writer send routes, and writer replacement state.
- `Writer task` owns outbound ME socket send progression. - `Writer task` owns outbound ME socket send progression.
- `Reader task` owns inbound ME socket parsing and event dispatch. - `Reader task` owns inbound ME socket parsing and event dispatch.
@@ -188,6 +196,8 @@ Data Plane should avoid waiting on operations that are not strictly required for
- Shared maps use fine-grained, short-lived locking. - Shared maps use fine-grained, short-lived locking.
- Read-mostly paths avoid broad write-lock windows. - Read-mostly paths avoid broad write-lock windows.
- Backpressure decisions are localized at route/channel boundary. - Backpressure decisions are localized at route/channel boundary.
- Generation and replacement commits use the lock order `writers -> registry binding -> reinit coordinator`.
- After acquiring the registry publication guard, a commit has no cancellation point before publication and retirement state are made consistent.
Design target: Design target:
- A slow consumer should degrade only itself (or its route), not global writer progress. - A slow consumer should degrade only itself (or its route), not global writer progress.
@@ -196,6 +206,7 @@ Design target:
Writer and reader loops are cancellation-aware: Writer and reader loops are cancellation-aware:
- explicit cancel token / close command support; - explicit cancel token / close command support;
- safe unbind and cleanup via registry; - safe unbind and cleanup via registry;
- RAII replacement reservations restore `Preparing` to `Open` when preparation is cancelled before commit;
- deterministic order: stop admission -> drain/close -> release resources. - deterministic order: stop admission -> drain/close -> release resources.
## Consistency Model ## Consistency Model
@@ -208,9 +219,10 @@ For one `conn_id`:
### Generation Consistency ### Generation Consistency
Generational consistency guarantees: Generational consistency guarantees:
- New generation is not promoted before minimum coverage gate. - Commit revalidates generation, desired-map hash, endpoint revision, and fresh coverage while holding the publication barriers.
- Previous generation remains available in `draining` state during handover. - Promotion requires `me_pool_min_fresh_ratio`; missing DC-family groups additionally require a stale-binding mode other than `never`.
- Forced retirement is policy-bound (`drain ttl`, optional force-close), not immediate. - Previous-generation writers are retained only where the selected stale-fallback policy requires them. Covered writers become ineligible at commit and may close immediately afterward.
- Draining writers are removed when empty, at the effective force-close deadline (`0` first selects the 300-second safety fallback, then the drain TTL remains a lower bound), or by threshold/control-path eviction; drain TTL alone only triggers warnings.
### Policy Consistency ### Policy Consistency
Policy changes (`adaptive/static floor`, fallback mode, retries) should apply without violating established active-session routing invariants. Policy changes (`adaptive/static floor`, fallback mode, retries) should apply without violating established active-session routing invariants.
+24 -12
View File
@@ -57,12 +57,14 @@ Refill работает асинхронно и не должен блокиро
`Registry` — маршрутизационный индекс между ME и клиентскими сессиями: `Registry` — маршрутизационный индекс между ME и клиентскими сессиями:
- `conn_id -> канал ответа клиенту`; - `conn_id -> канал ответа клиенту`;
- map биндов `conn_id <-> writer_id`; - map биндов `conn_id <-> writer_id`;
- send routes writer-ов и их replacement state;
- снимки активности writer-ов и idle-трекинг. - снимки активности writer-ов и idle-трекинг.
Ключевые инварианты: Ключевые инварианты:
- один `conn_id` маршрутизируется максимум в один активный канал ответа; - один `conn_id` маршрутизируется максимум в один активный канал ответа;
- потеря writer-а приводит к безопасному unbind/cleanup и отправке close; - потеря writer-а приводит к безопасному unbind/cleanup и отправке close;
- именно `Registry` является источником истины по активным ME-биндам. - именно `Registry` является источником истины по активным ME-биндам.
- binding lock registry линеаризует client binds, публикацию writer-а и переход, закрывающий replacement victim для новых binds.
## Adaptive Floor ## Adaptive Floor
@@ -100,7 +102,9 @@ Refill работает асинхронно и не должен блокиро
### Логика переходов ### Логика переходов
- `Warm -> Active`: когда достигнуты условия покрытия/готовности. - `Warm -> Active`: когда достигнуты условия покрытия/готовности.
- `Active -> Draining`: при swap поколения, замене endpoint или контролируемом выводе. - `Active -> Draining`: при swap поколения, замене endpoint или контролируемом выводе.
- `Draining -> removed`: после drain TTL/force-close политики (или естественного опустошения). - `Draining -> removed`: после естественного опустошения, при effective force-close deadline либо через threshold/control-path eviction. `me_pool_drain_ttl_secs` — порог предупреждений и нижняя граница force-close, а не самостоятельный deadline удаления.
Writer replacement имеет отдельный registry-local lifecycle: `Open -> Preparing -> Retiring`. `Preparing` исключает дублирующую replacement work, но намеренно разрешает новые client binds. Commit повторно проверяет victim под binding lock; `Retiring` отклоняет новые binds. Отмена незакоммиченного reservation возвращает `Open`, а contour writer-а независимо остаётся `Warm`, `Active` или `Draining`.
Такое разделение снижает SPOF-риски и делает cutover предсказуемым. Такое разделение снижает SPOF-риски и делает cutover предсказуемым.
@@ -111,14 +115,17 @@ Generation изолирует эпохи пула при reinit/reconfiguration.
### Фазы жизненного цикла ### Фазы жизненного цикла
1. `Bootstrap`: поднимается начальный набор writer-ов. 1. `Bootstrap`: поднимается начальный набор writer-ов.
2. `Warmup`: создаётся и валидируется новое поколение. 2. `Warmup`: создаётся и валидируется новое поколение.
3. `Activation`: новое поколение становится active после прохождения coverage-gate. 3. `Activation`: generation атомарно становится active после commit-time проверки настроенной доли coverage и stale-binding policy.
4. `Drain`: предыдущее поколение переводится в draining, текущим сессиям дают завершиться. 4. `Drain`: подходящие по policy старые writers сохраняются как ограниченный stale fallback; покрытые старые writers становятся недоступны для новых binds и входят в retirement во время commit, после чего могут закрыться сразу по его завершении.
5. `Retire`: старое поколение удаляется по graceful-правилам. 5. `Retire`: draining writers удаляются после опустошения либо по force-close, threshold или явной control policy.
### Операционные гарантии ### Операционные гарантии
- нельзя активировать поколение частично без минимального покрытия; - activation атомарна, но committed topology может содержать отсутствующие DC-family groups, если достигнут `me_pool_min_fresh_ratio` и `me_bind_stale_mode` разрешает ограниченный stale fallback. Режим `never` запрещает отсутствующие groups;
- healthy-клиенты не должны теряться только из-за появления нового поколения; - generation handover ограничен policy и не даёт универсальной zero-drop гарантии: покрытые старые writers могут retire с закрытием их sessions сразу после commit, а для непокрытых groups сохраняются только выбранные stale writers;
- draining-поколение служит буфером для in-flight трафика во время swap. - pending generation владеет только writer-ами, принятыми для её generation и текущей endpoint map; устаревшие задачи не могут публиковать состояние в более новую generation;
- pending generation привязана к desired-map hash и endpoint revision и переиспользуется не более 1800 секунд;
- replacement сначала подготавливает successor; под единым binding guard commit сначала переводит predecessor в `Retiring`, а затем регистрирует successor до освобождения guard. Неудачная или отменённая до этой границы подготовка сохраняет predecessor и освобождает reservation;
- pool-state telemetry публикует число и дефицит pending writers, отсутствующие DC-family groups, актуальность map, orphan warm writers и счётчики фаз replacement `preparing`/`retiring`.
### Готовность и приём клиентов ### Готовность и приём клиентов
Готовность пула не равна "все endpoint полностью насыщены". Готовность пула не равна "все endpoint полностью насыщены".
@@ -149,8 +156,9 @@ Runtime специально разделён на две плоскости:
### Модель владения состоянием ### Модель владения состоянием
Владение разделено по доменам: Владение разделено по доменам:
- `MePool` владеет жизненным циклом writer-ов и policy-state. - `MePool` владеет inventory writer-ов, contour lifecycle и runtime policy state.
- `Registry` владеет routing-биндами клиентских сессий. - Reinit coordinator владеет authority active/pending generation, привязанной к map hash и endpoint revision.
- `Registry` владеет routing-биндами клиентских сессий, send routes writer-ов и replacement state.
- `Writer task` владеет исходящей прогрессией ME-сокета. - `Writer task` владеет исходящей прогрессией ME-сокета.
- `Reader task` владеет входящим парсингом и dispatch-событиями. - `Reader task` владеет входящим парсингом и dispatch-событиями.
@@ -188,6 +196,8 @@ Data Plane не должен ждать операций, не критичны
- Для shared map используются короткие и узкие lock-секции. - Для shared map используются короткие и узкие lock-секции.
- Read-heavy пути избегают длительных write-lock окон. - Read-heavy пути избегают длительных write-lock окон.
- Решения по backpressure локализованы на границе route/channel. - Решения по backpressure локализованы на границе route/channel.
- Generation и replacement commits используют порядок locks `writers -> registry binding -> reinit coordinator`.
- После получения registry publication guard до согласованной публикации и retirement state нет cancellation point.
Цель: Цель:
- медленный consumer должен деградировать локально, не останавливая глобальный прогресс writer-а. - медленный consumer должен деградировать локально, не останавливая глобальный прогресс writer-а.
@@ -196,6 +206,7 @@ Data Plane не должен ждать операций, не критичны
Reader/Writer loop должны быть cancellation-aware: Reader/Writer loop должны быть cancellation-aware:
- явные cancel token / close command; - явные cancel token / close command;
- безопасный unbind/cleanup через registry; - безопасный unbind/cleanup через registry;
- RAII replacement reservations возвращают `Preparing` в `Open`, если подготовка отменена до commit;
- детерминированный порядок: stop admission -> drain/close -> release resources. - детерминированный порядок: stop admission -> drain/close -> release resources.
## Модель согласованности ## Модель согласованности
@@ -208,9 +219,10 @@ Reader/Writer loop должны быть cancellation-aware:
### Согласованность поколения ### Согласованность поколения
Гарантии generation: Гарантии generation:
- новое поколение не активируется до прохождения минимального coverage-gate; - commit повторно проверяет generation, desired-map hash, endpoint revision и fresh coverage под publication barriers;
- предыдущее поколение остаётся в `draining` на время handover; - promotion требует `me_pool_min_fresh_ratio`; отсутствующие DC-family groups дополнительно требуют stale-binding mode, отличного от `never`;
- принудительный вывод writer-ов ограничен policy (`drain ttl`, optional force-close), а не мгновенный. - writers предыдущей generation сохраняются только там, где их требует выбранная stale-fallback policy. Покрытые writers становятся недоступны для новых binds при commit и могут закрыться сразу после него;
- draining writers удаляются после опустошения, при effective force-close deadline (`0` сначала выбирает safety fallback 300 секунд, после чего drain TTL остаётся нижней границей) либо по threshold/control-path eviction; один drain TTL только вызывает предупреждения.
### Согласованность политик ### Согласованность политик
Изменение policy (`adaptive/static floor`, fallback mode, retries) не должно ломать инварианты маршрутизации уже активных сессий. Изменение policy (`adaptive/static floor`, fallback mode, retries) не должно ломать инварианты маршрутизации уже активных сессий.
+115 -55
View File
@@ -10,10 +10,10 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
> >
> Die in diesem Dokument beschriebenen Konfigurationsparameter richten sich an erfahrene Nutzer und dienen dem Feintuning. Änderungen ohne klares Verständnis der jeweiligen Funktion können zu Instabilität oder anderem unerwarteten Verhalten führen. Gehen Sie entsprechend vorsichtig und auf eigenes Risiko vor. > Die in diesem Dokument beschriebenen Konfigurationsparameter richten sich an erfahrene Nutzer und dienen dem Feintuning. Änderungen ohne klares Verständnis der jeweiligen Funktion können zu Instabilität oder anderem unerwarteten Verhalten führen. Gehen Sie entsprechend vorsichtig und auf eigenes Risiko vor.
> `Hot-Reload` zeigt an, ob ein geänderter Wert vom Config-Watcher ohne Prozessneustart übernommen wird; `✘` bedeutet, dass für den Runtime-Effekt ein Neustart erforderlich ist. > `Hot-Reload` zeigt an, ob der Config-Watcher einen geänderten Wert direkt übernimmt. `✘` bedeutet, dass der Watcher ihn nicht anwendet; je nach Feld ist für die vollständige Wirkung ein prozessinterner Runtime-Generation-Reload oder ein Prozessneustart erforderlich.
# Inhaltsverzeichnis # Inhaltsverzeichnis
- [Schlüssel auf oberster Ebene](#top-level-keys) - [Schlüssel auf oberster Ebene](#schlüssel-auf-oberster-ebene)
- [logging](#logging) - [logging](#logging)
- [general](#general) - [general](#general)
- [general.modes](#generalmodes) - [general.modes](#generalmodes)
@@ -65,10 +65,10 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
- **Beispiel**: - **Beispiel**:
```toml ```toml
# Links für alle konfigurierten User anzeigen # Show links for all configured users
show_link = "*" show_link = "*"
# oder: Links nur für ausgewählte User anzeigen # Or show links only for selected users
# show_link = ["alice", "bob"] # show_link = ["alice", "bob"]
``` ```
## dc_overrides ## dc_overrides
@@ -87,8 +87,8 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
- **Beispiel**: - **Beispiel**:
```toml ```toml
# Wenn ein Client ein unbekanntes/nicht standardisiertes DC ohne Override anfordert, # When a client requests an unknown or non-standard DC without an override,
# wird er an diesen Default-Cluster weitergeleitet (1..=5). # route it to this default cluster (1..=5).
default_dc = 2 default_dc = 2
``` ```
@@ -204,6 +204,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` | | [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` |
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` | | [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` |
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` | | [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` |
| [`me_writer_byte_budget_bytes`](#me_writer_byte_budget_bytes) | `usize` | `33570816` | `✘` |
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` | | [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` |
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` | | [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` |
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` | | [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` |
@@ -216,6 +217,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
| [`me_d2c_frame_buf_shrink_threshold_bytes`](#me_d2c_frame_buf_shrink_threshold_bytes) | `usize` | `262144` | `✔` | | [`me_d2c_frame_buf_shrink_threshold_bytes`](#me_d2c_frame_buf_shrink_threshold_bytes) | `usize` | `262144` | `✔` |
| [`direct_relay_copy_buf_c2s_bytes`](#direct_relay_copy_buf_c2s_bytes) | `usize` | `65536` | `✔` | | [`direct_relay_copy_buf_c2s_bytes`](#direct_relay_copy_buf_c2s_bytes) | `usize` | `65536` | `✔` |
| [`direct_relay_copy_buf_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` | | [`direct_relay_copy_buf_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` |
| [`direct_relay_buffer_budget_max_bytes`](#direct_relay_buffer_budget_max_bytes) | `usize` | `0` | `✘` |
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` | | [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` |
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` | | [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` |
| [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` | | [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` |
@@ -301,13 +303,14 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
| [`me_pool_drain_soft_evict_per_writer`](#me_pool_drain_soft_evict_per_writer) | `u8` | `2` | `✘` | | [`me_pool_drain_soft_evict_per_writer`](#me_pool_drain_soft_evict_per_writer) | `u8` | `2` | `✘` |
| [`me_pool_drain_soft_evict_budget_per_core`](#me_pool_drain_soft_evict_budget_per_core) | `u16` | `16` | `✘` | | [`me_pool_drain_soft_evict_budget_per_core`](#me_pool_drain_soft_evict_budget_per_core) | `u16` | `16` | `✘` |
| [`me_pool_drain_soft_evict_cooldown_ms`](#me_pool_drain_soft_evict_cooldown_ms) | `u64` | `1000` | `✘` | | [`me_pool_drain_soft_evict_cooldown_ms`](#me_pool_drain_soft_evict_cooldown_ms) | `u64` | `1000` | `✘` |
| [`me_bind_stale_mode`](#me_bind_stale_mode) | `"never"`, `"ttl"` oder `"always"` | `"ttl"` | `✔` | | [`me_bind_stale_mode`](#me_bind_stale_mode) | `"never"`, `"ttl"` oder `"always"` | `"never"` | `✔` |
| [`me_bind_stale_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` | | [`me_bind_stale_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` |
| [`me_pool_min_fresh_ratio`](#me_pool_min_fresh_ratio) | `f32` | `0.8` | `✔` | | [`me_pool_min_fresh_ratio`](#me_pool_min_fresh_ratio) | `f32` | `0.8` | `✔` |
| [`me_reinit_drain_timeout_secs`](#me_reinit_drain_timeout_secs) | `u64` | `90` | `✔` | | [`me_reinit_drain_timeout_secs`](#me_reinit_drain_timeout_secs) | `u64` | `90` | `✔` |
| [`proxy_secret_auto_reload_secs`](#proxy_secret_auto_reload_secs) | `u64` | `3600` | `✔` | | [`proxy_secret_auto_reload_secs`](#proxy_secret_auto_reload_secs) | `u64` | `3600` | `✔` |
| [`proxy_config_auto_reload_secs`](#proxy_config_auto_reload_secs) | `u64` | `3600` | `✔` | | [`proxy_config_auto_reload_secs`](#proxy_config_auto_reload_secs) | `u64` | `3600` | `✔` |
| [`me_reinit_singleflight`](#me_reinit_singleflight) | `bool` | `true` | `✔` | | [`me_reinit_singleflight`](#me_reinit_singleflight) | `bool` | `true` | `✔` |
| [`me_reinit_max_concurrency`](#me_reinit_max_concurrency) | `usize` | `2` | `✔` |
| [`me_reinit_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` | `✘` | | [`me_reinit_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` | `✘` |
| [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` | `✔` | | [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` | `✔` |
| [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` | `✔` | | [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` | `✔` |
@@ -346,6 +349,8 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
[general] [general]
config_strict = true config_strict = true
``` ```
- **Bekannte Einschränkung**: In dieser Revision weist `config_strict = true` die ansonsten unterstützten Schlüssel `access.user_source_deny` und `[[upstreams]].prefer` zurück. Lassen Sie den Strict-Modus deaktiviert, wenn einer dieser Schlüssel verwendet wird.
## prefer_ipv6 ## prefer_ipv6
- **Einschränkungen / Validierung**: Veraltet. Verwenden Sie `network.prefer`. - **Einschränkungen / Validierung**: Veraltet. Verwenden Sie `network.prefer`.
- **Beschreibung**: Veraltetes Legacy-Einstellungsflag IPv6 wurde nach `network.prefer` migriert. - **Beschreibung**: Veraltetes Legacy-Einstellungsflag IPv6 wurde nach `network.prefer` migriert.
@@ -482,8 +487,8 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
stun_nat_probe_concurrency = 8 stun_nat_probe_concurrency = 8
``` ```
## middle_proxy_pool_size ## middle_proxy_pool_size
- **Einschränkungen / Validierung**: `usize`. Der effektive Wert ist `max(value, 1)` zur Runtime (daher verhält sich `0` wie `1`). - **Einschränkungen / Validierung**: `usize`. Der an die ME-Initialisierung übergebene Wert wird als `max(value, 1)` normalisiert.
- **Beschreibung**: Zielgröße des aktiven ME Writer-Pools. - **Beschreibung**: Nicht erzwingender Kompatibilitätswert, der derzeit im ME-Initialisierungslog ausgegeben wird. Aktive Writer-Ziele werden aus der DC-Family-Floor-Policy abgeleitet, nicht aus diesem Wert.
- **Beispiel**: - **Beispiel**:
```toml ```toml
@@ -574,16 +579,25 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
rpc_proxy_req_every = 0 rpc_proxy_req_every = 0
``` ```
## me_writer_cmd_channel_capacity ## me_writer_cmd_channel_capacity
- **Einschränkungen / Validierung**: Muss `> 0` sein. - **Einschränkungen / Validierung**: Muss innerhalb von `1..=16384` liegen.
- **Beschreibung**: Kapazität des Befehlskanals pro Autor. - **Beschreibung**: Kapazität des Befehlskanals pro ME-Writer.
- **Beispiel**: - **Beispiel**:
```toml ```toml
[general] [general]
me_writer_cmd_channel_capacity = 4096 me_writer_cmd_channel_capacity = 4096
``` ```
## me_writer_byte_budget_bytes
- **Einschränkungen / Validierung**: Muss ein Vielfaches von `16384` zwischen dem dynamischen Minimum und `268435456` sein. Das Minimum ist `2 * general.max_client_frame + 256`, auf `16384` aufgerundet; bei der Standard-Framegröße beträgt es `33570816`.
- **Beschreibung**: Residenter Speicheretat für die Daten-Queue jedes ME-Writers. Der Datei-Watcher baut vorhandene Writer für dieses Feld nicht neu; es wird wirksam, wenn über die API eine neue ME-/Runtime-Generation erstellt wird oder nach einem Neustart.
- **Beispiel**:
```toml
[general]
me_writer_byte_budget_bytes = 33570816
```
## me_route_channel_capacity ## me_route_channel_capacity
- **Einschränkungen / Validierung**: Muss `> 0` sein. - **Einschränkungen / Validierung**: Muss innerhalb von `1..=8192` liegen.
- **Beschreibung**: Kapazität des ME-Antwortroutenkanals pro Verbindung. - **Beschreibung**: Kapazität des ME-Antwortroutenkanals pro Verbindung.
- **Beispiel**: - **Beispiel**:
@@ -592,7 +606,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
me_route_channel_capacity = 768 me_route_channel_capacity = 768
``` ```
## me_c2me_channel_capacity ## me_c2me_channel_capacity
- **Einschränkungen / Validierung**: Muss `> 0` sein. - **Einschränkungen / Validierung**: Muss innerhalb von `1..=8192` liegen.
- **Beschreibung**: Kapazität der Befehlswarteschlange pro Client (Client-Leser -> ME Absender). - **Beschreibung**: Kapazität der Befehlswarteschlange pro Client (Client-Leser -> ME Absender).
- **Beispiel**: - **Beispiel**:
@@ -690,6 +704,15 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
[general] [general]
direct_relay_copy_buf_s2c_bytes = 262144 direct_relay_copy_buf_s2c_bytes = 262144
``` ```
## direct_relay_buffer_budget_max_bytes
- **Einschränkungen / Validierung**: `0` oder ein Vielfaches von `4096` innerhalb von `16777216..=2147483648`.
- **Beschreibung**: Prozesseigene harte Obergrenze für Direct-Relay-Copy-Buffer; `0` leitet sie beim Prozessstart aus den cgroup-/Host-Speichergrenzen ab. Die Änderung wird bis zum Neustart zurückgestellt.
- **Beispiel**:
```toml
[general]
direct_relay_buffer_budget_max_bytes = 0
```
## crypto_pending_buffer ## crypto_pending_buffer
- **Einschränkungen / Validierung**: `usize` (Byte). - **Einschränkungen / Validierung**: `usize` (Byte).
- **Beschreibung**: Maximaler Puffer für ausstehenden Chiffretext pro Client-Writer (Byte). - **Beschreibung**: Maximaler Puffer für ausstehenden Chiffretext pro Client-Writer (Byte).
@@ -700,7 +723,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
crypto_pending_buffer = 262144 crypto_pending_buffer = 262144
``` ```
## max_client_frame ## max_client_frame
- **Einschränkungen / Validierung**: `usize` (Byte). - **Einschränkungen / Validierung**: Muss innerhalb von `4096..=16777216` (Byte) liegen.
- **Beschreibung**: Maximal zulässige Client-Framegröße MTProto (Byte). - **Beschreibung**: Maximal zulässige Client-Framegröße MTProto (Byte).
- **Beispiel**: - **Beispiel**:
@@ -1213,7 +1236,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
me_route_hybrid_max_wait_ms = 3000 me_route_hybrid_max_wait_ms = 3000
``` ```
## me_route_blocking_send_timeout_ms ## me_route_blocking_send_timeout_ms
- **Einschränkungen / Validierung**: Muss innerhalb von `0..=5000` (Millisekunden) liegen. `0` behält das alte unbegrenzte Warteverhalten bei. - **Einschränkungen / Validierung**: Muss innerhalb von `1..=5000` (Millisekunden) liegen.
- **Beschreibung**: Maximale Wartezeit für das Blockieren des Route-Channel-Sende-Fallbacks. - **Beschreibung**: Maximale Wartezeit für das Blockieren des Route-Channel-Sende-Fallbacks.
- **Beispiel**: - **Beispiel**:
@@ -1291,7 +1314,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Standard: 3 (erlaubter Bereich: 0..=10) # Default: 3 (allowed range: 0..=10)
me_hardswap_warmup_extra_passes = 3 me_hardswap_warmup_extra_passes = 3
``` ```
## me_hardswap_warmup_pass_backoff_base_ms ## me_hardswap_warmup_pass_backoff_base_ms
@@ -1301,7 +1324,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Standard: 500 # Default: 500
me_hardswap_warmup_pass_backoff_base_ms = 500 me_hardswap_warmup_pass_backoff_base_ms = 500
``` ```
## me_config_stable_snapshots ## me_config_stable_snapshots
@@ -1311,7 +1334,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# erfordern drei identische Snapshots, bevor ME Endpunktkartenaktualisierungen angewendet werden # Require three identical snapshots before applying ME endpoint map updates
me_config_stable_snapshots = 3 me_config_stable_snapshots = 3
``` ```
## me_config_apply_cooldown_secs ## me_config_apply_cooldown_secs
@@ -1321,7 +1344,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# erlaubt die sofortige Anwendung stabiler Snapshots (keine Abklingzeit) # Allow applying stable snapshots immediately without a cooldown
me_config_apply_cooldown_secs = 0 me_config_apply_cooldown_secs = 0
``` ```
## me_snapshot_require_http_2xx ## me_snapshot_require_http_2xx
@@ -1331,7 +1354,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# ermöglicht das Anwenden von Snapshots, auch wenn der HTTP-Status nicht 2xx ist # Allow applying snapshots even when the HTTP status is not 2xx
me_snapshot_require_http_2xx = false me_snapshot_require_http_2xx = false
``` ```
## me_snapshot_reject_empty_map ## me_snapshot_reject_empty_map
@@ -1341,7 +1364,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Anwenden leerer Snapshots zulassen (mit Vorsicht verwenden) # Allow applying empty snapshots with care
me_snapshot_reject_empty_map = false me_snapshot_reject_empty_map = false
``` ```
## me_snapshot_min_proxy_for_lines ## me_snapshot_min_proxy_for_lines
@@ -1351,7 +1374,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# erfordern mindestens 10 Proxy_for-Zeilen, bevor ein Snapshot akzeptiert wird # Require at least 10 proxy_for rows before accepting a snapshot
me_snapshot_min_proxy_for_lines = 10 me_snapshot_min_proxy_for_lines = 10
``` ```
## proxy_secret_stable_snapshots ## proxy_secret_stable_snapshots
@@ -1361,7 +1384,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# erfordern zwei identische getProxySecret-Snapshots, bevor sie zur Runtime rotieren # Require two identical getProxySecret snapshots before rotating at runtime
proxy_secret_stable_snapshots = 2 proxy_secret_stable_snapshots = 2
``` ```
## proxy_secret_rotate_runtime ## proxy_secret_rotate_runtime
@@ -1371,7 +1394,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Deaktivieren Sie die Proxy-Secret-Rotation zur Runtime (Start verwendet weiterhin Proxy_secret_path/proxy_secret_len_max) # Disable runtime proxy-secret rotation; startup still uses proxy_secret_path/proxy_secret_len_max
proxy_secret_rotate_runtime = false proxy_secret_rotate_runtime = false
``` ```
## me_secret_atomic_snapshot ## me_secret_atomic_snapshot
@@ -1381,7 +1404,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# HINWEIS: Wenn use_middle_proxy=true, wird Telemt dies beim Laden automatisch aktivieren # Telemt enables this automatically during load when use_middle_proxy=true
me_secret_atomic_snapshot = false me_secret_atomic_snapshot = false
``` ```
## proxy_secret_len_max ## proxy_secret_len_max
@@ -1391,17 +1414,17 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Standard: 256 (Byte) # Default: 256 bytes
proxy_secret_len_max = 256 proxy_secret_len_max = 256
``` ```
## me_pool_drain_ttl_secs ## me_pool_drain_ttl_secs
- **Einschränkungen / Validierung**: `u64` (Sekunden). `0` deaktiviert das Drain-TTL-Fenster (und unterdrückt Drain-TTL-Warnungen für nicht leere Draining-Writer). - **Einschränkungen / Validierung**: `u64` (Sekunden). `0` deaktiviert das Drain-TTL-Fenster (und unterdrückt Drain-TTL-Warnungen für nicht leere Draining-Writer).
- **Beschreibung**: Drain-TTL-Zeitfenster für stale ME-Writer nach Änderungen der Endpoint-Map. Während der TTL dürfen stale Writer nur als Fallback für neue Bindungen verwendet werden (abhängig von der Bindungsrichtlinie). - **Beschreibung**: Altersschwelle für Warnungen bei langem Drain nach Endpoint-Map-Änderungen und Untergrenze bei der Normalisierung des Force-Close-Timeouts. Stale-Bind-Zulassung wird separat durch `me_bind_stale_mode` und `me_bind_stale_ttl_secs` gesteuert.
- **Beispiel**: - **Beispiel**:
```toml ```toml
[general] [general]
# Drain TTL deaktivieren (Draining Writer geben keine „Past Drain TTL“-Warnungen aus) # Disable drain TTL warnings for writers that remain draining past the threshold
me_pool_drain_ttl_secs = 0 me_pool_drain_ttl_secs = 0
``` ```
## me_instadrain ## me_instadrain
@@ -1476,17 +1499,17 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
``` ```
## me_bind_stale_mode ## me_bind_stale_mode
- **Einschränkungen / Validierung**: `"never"`, `"ttl"` oder `"always"`. - **Einschränkungen / Validierung**: `"never"`, `"ttl"` oder `"always"`.
- **Beschreibung**: Policy für neue Binds auf stale draining Writern. - **Beschreibung**: Policy für neue Binds auf stale draining Writern in nicht abgedeckten DC-Family-Gruppen. Der Default `never` verlangt vollständige Gruppenabdeckung, bevor ein partieller Hardswap committen darf; `ttl` und `always` erlauben policy-begrenzten Fallback.
- **Beispiel**: - **Beispiel**:
```toml ```toml
[general] [general]
# veraltete Bindungen nur für ein begrenztes Zeitfenster zulassen # Allow stale binds only for a limited time window
me_bind_stale_mode = "ttl" me_bind_stale_mode = "ttl"
``` ```
## me_bind_stale_ttl_secs ## me_bind_stale_ttl_secs
- **Einschränkungen / Validierung**: `u64`. - **Einschränkungen / Validierung**: `u64`.
- **Beschreibung**: TTL für stale Bind-Zulassung, wenn der stale mode `ttl` ist. - **Beschreibung**: TTL für stale Bind-Zulassung im Modus `ttl`; `0` deaktiviert den TTL-Ablauf für zulässige draining Writer.
- **Beispiel**: - **Beispiel**:
```toml ```toml
@@ -1496,22 +1519,22 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
``` ```
## me_pool_min_fresh_ratio ## me_pool_min_fresh_ratio
- **Einschränkungen / Validierung**: Muss innerhalb von `[0.0, 1.0]` liegen. - **Einschränkungen / Validierung**: Muss innerhalb von `[0.0, 1.0]` liegen.
- **Beschreibung**: Mindestanteil frischer Desired-DC-Coverage, bevor stale Writer gedraint werden. - **Beschreibung**: Mindestanteil frischer DC-Family-Coverage beim Generation-Commit. Fehlende Gruppen blockieren den Commit unter `me_bind_stale_mode = "never"` auch dann, wenn dieses Verhältnis erreicht ist.
- **Beispiel**: - **Beispiel**:
```toml ```toml
[general] [general]
# erfordern >=90 % der gewünschten DC-Abdeckung, bevor stale Writer gedraint werden # Require at least 90% desired-DC coverage before draining stale writers
me_pool_min_fresh_ratio = 0.9 me_pool_min_fresh_ratio = 0.9
``` ```
## me_reinit_drain_timeout_secs ## me_reinit_drain_timeout_secs
- **Einschränkungen / Validierung**: `u64`. `0` verwendet das Runtime-Sicherheits-Fallback-Timeout für erzwungenes Schließen. Wenn `> 0` und `< me_pool_drain_ttl_secs`, erhöht die Runtime den Wert auf TTL. - **Einschränkungen / Validierung**: `u64`. `0` wählt zuerst den 300-Sekunden-Runtime-Sicherheitsfallback; anschließend wird das effektive Timeout mindestens auf `me_pool_drain_ttl_secs` angehoben.
- **Beschreibung**: Force-Close-Timeout für draining stale Writer. Bei der Einstellung `0` entspricht das effektive Timeout dem Runtime-Safety-Fallback (300 Sekunden). - **Beschreibung**: Force-Close-Timeout für draining stale Writer. Der effektive Wert ist das Maximum aus dem konfigurierten Wert ungleich Null (oder 300 Sekunden bei `0`) und der Drain-TTL.
- **Beispiel**: - **Beispiel**:
```toml ```toml
[general] [general]
# Runtime-Safety-Fallback-Force-Close-Timeout (300 s) verwenden # Use the runtime safety fallback force-close timeout of 300 seconds
me_reinit_drain_timeout_secs = 0 me_reinit_drain_timeout_secs = 0
``` ```
## proxy_secret_auto_reload_secs ## proxy_secret_auto_reload_secs
@@ -1521,10 +1544,10 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Legacy-Modus: update_every weglassen, um Proxy_*_auto_reload_secs zu verwenden # Legacy mode: omit update_every to use proxy_*_auto_reload_secs
proxy_secret_auto_reload_secs = 600 proxy_secret_auto_reload_secs = 600
proxy_config_auto_reload_secs = 120 proxy_config_auto_reload_secs = 120
# effektives Aktualisierungsintervall = min(600, 120) = 120 Sekunden # Effective updater interval = min(600, 120) = 120 seconds
``` ```
## proxy_config_auto_reload_secs ## proxy_config_auto_reload_secs
- **Einschränkungen / Validierung**: Veraltet. Verwenden Sie `general.update_every`. Wenn `general.update_every` nicht explizit festgelegt ist, beträgt das effektive Legacy-Aktualisierungsintervall `min(proxy_secret_auto_reload_secs, proxy_config_auto_reload_secs)` und muss `> 0` betragen. - **Einschränkungen / Validierung**: Veraltet. Verwenden Sie `general.update_every`. Wenn `general.update_every` nicht explizit festgelegt ist, beträgt das effektive Legacy-Aktualisierungsintervall `min(proxy_secret_auto_reload_secs, proxy_config_auto_reload_secs)` und muss `> 0` betragen.
@@ -1533,10 +1556,10 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general] [general]
# Legacy-Modus: update_every weglassen, um Proxy_*_auto_reload_secs zu verwenden # Legacy mode: omit update_every to use proxy_*_auto_reload_secs
proxy_secret_auto_reload_secs = 600 proxy_secret_auto_reload_secs = 600
proxy_config_auto_reload_secs = 120 proxy_config_auto_reload_secs = 120
# effektives Aktualisierungsintervall = min(600, 120) = 120 Sekunden # Effective updater interval = min(600, 120) = 120 seconds
``` ```
## me_reinit_singleflight ## me_reinit_singleflight
- **Einschränkungen / Validierung**: `bool`. - **Einschränkungen / Validierung**: `bool`.
@@ -1547,9 +1570,18 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
[general] [general]
me_reinit_singleflight = true me_reinit_singleflight = true
``` ```
## me_reinit_max_concurrency
- **Einschränkungen / Validierung**: Muss innerhalb von `[1, 8]` liegen. Der effektive Wert ist `1`, solange `me_reinit_singleflight = true` ist.
- **Beschreibung**: Begrenzt gleichzeitige Warmups von ME-Generationen; zusätzliche Trigger werden zu genau einem ausstehenden Wiederholungslauf zusammengeführt.
- **Beispiel**:
```toml
[general]
me_reinit_max_concurrency = 2
```
## me_reinit_trigger_channel ## me_reinit_trigger_channel
- **Einschränkungen / Validierung**: Muss `> 0` sein. - **Einschränkungen / Validierung**: Muss innerhalb von `[1, 4096]` liegen.
- **Beschreibung**: Trigger-Queue-Kapazität für Reinit-Planer. - **Beschreibung**: Trigger-Queue-Kapazität für den Reinit-Planer. Eine neue Runtime-Generation erstellt ihren Kanal aus diesem Wert; der Datei-Watcher allein ändert die Größe des aktiven Kanals nicht.
- **Beispiel**: - **Beispiel**:
```toml ```toml
@@ -1699,7 +1731,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[general.links] [general.links]
show = "*" show = "*"
# oder: # Or:
# show = ["alice", "bob"] # show = ["alice", "bob"]
``` ```
## public_host ## public_host
@@ -1792,10 +1824,10 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[network] [network]
# IPv6 explizit aktivieren # Enable IPv6 explicitly
ipv6 = true ipv6 = true
# oder: IPv6 explizit deaktivieren # Or disable IPv6 explicitly
# ipv6 = false # ipv6 = false
``` ```
## prefer ## prefer
@@ -1972,7 +2004,7 @@ Dieses Dokument listet alle Konfigurationsschlüssel auf, die `config.toml` akze
```toml ```toml
[server] [server]
# Erzwingen Sie die Aktivierung von TCP, auch wenn auch ein Unix-Socket gebunden wird # Force-enable TCP even when also binding a Unix socket
listen_unix_sock = "/run/telemt.sock" listen_unix_sock = "/run/telemt.sock"
listen_tcp = true listen_tcp = true
``` ```
@@ -2561,6 +2593,8 @@ Der WEB-Modus transportiert MTProxy-Datenverkehr von Telegram Desktop über HTTP
| `carriers` | `false` oder ein nicht leeres Array eindeutiger Carrier | `false` | `✔` | | `carriers` | `false` oder ein nicht leeres Array eindeutiger Carrier | `false` | `✔` |
| `carrier_learning` | `bool` | `true` | `✔` | | `carrier_learning` | `bool` | `true` | `✔` |
| `carrier_negotiation_aggressiveness` | `"conservative"`, `"balanced"` oder `"aggressive"` | `"conservative"` | `✔` | | `carrier_negotiation_aggressiveness` | `"conservative"`, `"balanced"` oder `"aggressive"` | `"conservative"` | `✔` |
| `decoy_fasttrack_mode` | `"off"`, `"shadow"` oder `"enforce"` | `"off"` | `✘` |
| `http_connection_capacity_action` | `"drop"`, `"wait"` oder `"respond"` | `"drop"` | `✔` |
| `debug` | Tabelle | deaktiviert, begrenzte Defaults | `✔` | | `debug` | Tabelle | deaktiviert, begrenzte Defaults | `✔` |
| `limits` | Tabelle | begrenzte Defaults | `✘` | | `limits` | Tabelle | begrenzte Defaults | `✘` |
| `timeouts` | Tabelle | begrenzte Defaults | `✔` | | `timeouts` | Tabelle | begrenzte Defaults | `✔` |
@@ -2570,6 +2604,10 @@ Der WEB-Modus transportiert MTProxy-Datenverkehr von Telegram Desktop über HTTP
Fehlt `carriers` oder ist es `false`, sind Auto-Negotiation und Lernen deaktiviert und `carrier` ist der einzige Modus. Ein nicht leeres `carriers`-Array aktiviert die Start-Negotiation in der konfigurierten Reihenfolge; `carrier` wird genau einmal als letzter Fallback angehängt. Leere Arrays, Duplikate und `true` werden abgelehnt. Der Client darf nur vor dem Carrier-Commit zum nächsten Kandidaten wechseln; nach dem Commit erfordert ein Carrier-Wechsel eine neue Sitzung. Ein nativer Client ohne Metadaten, einschließlich Telegram iOS, verwendet immer den konfigurierten festen `carrier`, auch bei aktivierter Negotiation. Das aktuelle iOS unterstützt nur `https`; solche Bereitstellungen müssen daher `carrier = "https"` setzen. Die CFNetwork- und Darwin-User-Agent-Klassifizierung leitet keine Carrier-Unterstützung ab. Explizite native iOS-Capabilities werden mit `{https}` geschnitten; andere explizite Client-Capabilities gelten wie gemeldet. Fehlt `carriers` oder ist es `false`, sind Auto-Negotiation und Lernen deaktiviert und `carrier` ist der einzige Modus. Ein nicht leeres `carriers`-Array aktiviert die Start-Negotiation in der konfigurierten Reihenfolge; `carrier` wird genau einmal als letzter Fallback angehängt. Leere Arrays, Duplikate und `true` werden abgelehnt. Der Client darf nur vor dem Carrier-Commit zum nächsten Kandidaten wechseln; nach dem Commit erfordert ein Carrier-Wechsel eine neue Sitzung. Ein nativer Client ohne Metadaten, einschließlich Telegram iOS, verwendet immer den konfigurierten festen `carrier`, auch bei aktivierter Negotiation. Das aktuelle iOS unterstützt nur `https`; solche Bereitstellungen müssen daher `carrier = "https"` setzen. Die CFNetwork- und Darwin-User-Agent-Klassifizierung leitet keine Carrier-Unterstützung ab. Explizite native iOS-Capabilities werden mit `{https}` geschnitten; andere explizite Client-Capabilities gelten wie gemeldet.
`http_connection_capacity_action` gilt erst, nachdem Telemt eine private WEB-TCP-Verbindung akzeptiert hat und `max_http_connections` ausgeschöpft ist. `drop` schließt wie bisher sofort. `respond` sendet eine leere wiederholbare `503 Service Unavailable` mit `Retry-After: 1`, `Cache-Control: no-store` und `Connection: close`. `wait` wartet höchstens `http_overload_timeout_ms` auf normale Kapazität und beginnt danach die übliche HTTP-Verarbeitung; bei Timeout wird dieselbe begrenzte `503` gesendet. Höchstens `max_http_overload_connections` akzeptierte Sockets dürfen außerhalb der normalen Kapazität warten oder antworten.
`decoy_fasttrack_mode` ist restart-only und betrifft nur Capability-Arbeit für `GET/HEAD` am konfigurierten Basis-Root. `off` behält den vollständigen Scan bei, `shadow` zählt geeignete Requests ohne den Scan zu überspringen, und `enforce` überspringt ihn nur für `HEAD` oder eine fehlende/nicht kanonische `bridge`-Query. Ein kanonisch geformtes Bridge-`GET` scannt immer alle Profile des ausgewählten vhost. Die Optimierung begrenzt keine feindlichen kanonischen Probes; `enforce` muss hinter dem produktiven TLS-Terminator auf Timing-Unterscheidbarkeit geprüft werden.
`carrier_learning` wirkt nur bei aktivierter Negotiation. Das Lernen ist prozesslokal, speicherresident, begrenzt und ausschließlich positiv: Nur ein Carrier, der den serverdefinierten Zustand healthy erreicht, liefert Evidenz. `conservative` erfordert die breiteste Evidenz und deaktiviert IP-Ranking, `balanced` verwendet mittlere User-Agent-/Profil-Schwellen sowie geeignete öffentliche IPs nur als Tie-Breaker, und `aggressive` reagiert auf die ersten begrenzten Samples. Vom Client gemeldete Fehler bleiben rein diagnostisch und erzeugen keine negative Evidenz. Ein Reload wendet die Richtlinie auf neue Negotiation-Ketten an und verwirft inkompatible gespeicherte Evidenz. Das Deaktivieren von WEB beendet die Ausgabe neuer Bridge- und Session-Zugangsdaten; zum Widerrufen aktiver Sitzungen eines einzelnen Benutzers verwenden Sie die Users-API. `carrier_learning` wirkt nur bei aktivierter Negotiation. Das Lernen ist prozesslokal, speicherresident, begrenzt und ausschließlich positiv: Nur ein Carrier, der den serverdefinierten Zustand healthy erreicht, liefert Evidenz. `conservative` erfordert die breiteste Evidenz und deaktiviert IP-Ranking, `balanced` verwendet mittlere User-Agent-/Profil-Schwellen sowie geeignete öffentliche IPs nur als Tie-Breaker, und `aggressive` reagiert auf die ersten begrenzten Samples. Vom Client gemeldete Fehler bleiben rein diagnostisch und erzeugen keine negative Evidenz. Ein Reload wendet die Richtlinie auf neue Negotiation-Ketten an und verwirft inkompatible gespeicherte Evidenz. Das Deaktivieren von WEB beendet die Ausgabe neuer Bridge- und Session-Zugangsdaten; zum Widerrufen aktiver Sitzungen eines einzelnen Benutzers verwenden Sie die Users-API.
# [web.debug] # [web.debug]
@@ -2580,6 +2618,7 @@ Diese hot-reload-fähige Tabelle steuert den prozesseigenen serverseitigen WEB-D
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `enabled` | `bool` | `false` | Aktiviert WEB-HTTP-, WebSocket-Message-, Frame- und Lifecycle-Debugdatensätze. | | `enabled` | `bool` | `false` | Aktiviert WEB-HTTP-, WebSocket-Message-, Frame- und Lifecycle-Debugdatensätze. |
| `capture_lifecycle` | `bool` | `true` | Zeichnet typisierte Bridge-, Sitzungs-, Stream-, Handshake-, Relay- und Close-Ereignisse auf. | | `capture_lifecycle` | `bool` | `true` | Zeichnet typisierte Bridge-, Sitzungs-, Stream-, Handshake-, Relay- und Close-Ereignisse auf. |
| `sideband` | `bool` | `false` | Aktiviert Lifecycle-Diagnostik der generierten Bridge; wirksam nur zusammen mit `enabled` und `capture_lifecycle`. |
| `capture_headers` | `bool` | `true` | Speichert Headernamen und nur ausdrücklich zugelassene Werte ohne Zugangsdaten. | | `capture_headers` | `bool` | `true` | Speichert Headernamen und nur ausdrücklich zugelassene Werte ohne Zugangsdaten. |
| `capture_timings` | `bool` | `true` | Speichert Zeitpunkte für Request-Body, fertige Response, Response-Body und WebSocket-Message-Verarbeitung. | | `capture_timings` | `bool` | `true` | Speichert Zeitpunkte für Request-Body, fertige Response, Response-Body und WebSocket-Message-Verarbeitung. |
| `capture_frames` | `bool` | `true` | Zerlegt begrenzte Carrier-Bodys in Frame-Typ, Stream-ID, Länge, WINDOW- und Fehlermetadaten, ohne die Frame-Nutzlast zusätzlich zu speichern. | | `capture_frames` | `bool` | `true` | Zerlegt begrenzte Carrier-Bodys in Frame-Typ, Stream-ID, Länge, WINDOW- und Fehlermetadaten, ohne die Frame-Nutzlast zusätzlich zu speichern. |
@@ -2591,6 +2630,8 @@ Diese hot-reload-fähige Tabelle steuert den prozesseigenen serverseitigen WEB-D
Eine Änderung von `enabled` oder einem Erfassungsfeld löscht gespeicherte Datensätze und verwirft Commits, die unter der vorherigen Policy-Epoche begonnen wurden. Ändert sich nur das standardmäßige oder maximale Beobachtungsfenster, bleiben kompatible Datensätze erhalten. `full` speichert den vollständigen Body eines erkannten Carriers nur bis `web.limits.max_body_bytes`; Decoy-Bodys bleiben immer auf einen Präfix begrenzt. Ein Präfix, der nur mit einer gleichzeitig erhöhten, neustartpflichtigen Kapazität zulässig wäre, wird zusammen mit `web.debug` bis zum Neustart zurückgestellt. URI-Queries werden nie gespeichert, Werte von Credential-Headern werden ausgelassen, Body-Kopien werden von bekannten WEB-Capabilities und Bearer-Tokens bereinigt und Profilschlüssel ausschließlich als domänengetrennter Fingerprint mit 16 Hex-Zeichen dargestellt. Eine Änderung von `enabled` oder einem Erfassungsfeld löscht gespeicherte Datensätze und verwirft Commits, die unter der vorherigen Policy-Epoche begonnen wurden. Ändert sich nur das standardmäßige oder maximale Beobachtungsfenster, bleiben kompatible Datensätze erhalten. `full` speichert den vollständigen Body eines erkannten Carriers nur bis `web.limits.max_body_bytes`; Decoy-Bodys bleiben immer auf einen Präfix begrenzt. Ein Präfix, der nur mit einer gleichzeitig erhöhten, neustartpflichtigen Kapazität zulässig wäre, wird zusammen mit `web.debug` bis zum Neustart zurückgestellt. URI-Queries werden nie gespeichert, Werte von Credential-Headern werden ausgelassen, Body-Kopien werden von bekannten WEB-Capabilities und Bearer-Tokens bereinigt und Profilschlüssel ausschließlich als domänengetrennter Fingerprint mit 16 Hex-Zeichen dargestellt.
Wenn `enabled`, `sideband` und `capture_lifecycle` alle aktiv sind, senden neu generierte Bridge-Seiten begrenzte einmalige Lifecycle-Ereignisse an die exakte konfigurierte Basis plus `api/v1/diagnostic`. Die Route ist Telemt-intern und keine öffentliche Control API. Bereits ausgegebene Bridge-Dokumente erhalten dieses Verhalten durch Reload nicht nachträglich.
Die authentifizierte JSON-Steuerung kann den Ring mit `POST /v1/runtime/web/debug/clear` explizit löschen. Die erforderliche prozessbezogene `runtime_instance` sperrt veraltete Controller, die zurückgegebene Epoche sperrt laufende Writer und `leased_bytes` meldet Speicher, der noch von bereits gerenderten Snapshots gehalten wird. Die authentifizierte JSON-Steuerung kann den Ring mit `POST /v1/runtime/web/debug/clear` explizit löschen. Die erforderliche prozessbezogene `runtime_instance` sperrt veraltete Controller, die zurückgegebene Epoche sperrt laufende Writer und `leased_bytes` meldet Speicher, der noch von bereits gerenderten Snapshots gehalten wird.
# [web.limits] # [web.limits]
@@ -2605,6 +2646,7 @@ Diese prozessweiten Obergrenzen begrenzen alle WEB-Register, Warteschlangen, Req
| `carrier_batch_bytes` | `usize` | `2097152` | Maximale Größe eines kodierten Downlink-Batches. | | `carrier_batch_bytes` | `usize` | `2097152` | Maximale Größe eines kodierten Downlink-Batches. |
| `max_frames_per_body` | `usize` | `4096` | Maximale Zahl geparster oder ausgegebener Frames pro Carrier-Body. | | `max_frames_per_body` | `usize` | `4096` | Maximale Zahl geparster oder ausgegebener Frames pro Carrier-Body. |
| `max_http_connections` | `usize` | `1024` | Prozessweit akzeptierte WEB-HTTP-Verbindungen. | | `max_http_connections` | `usize` | `1024` | Prozessweit akzeptierte WEB-HTTP-Verbindungen. |
| `max_http_overload_connections` | `usize` | `64` | Akzeptierte überlastete Sockets, die außerhalb normaler HTTP-Kapazität warten oder eine begrenzte wiederholbare Antwort senden dürfen. |
| `max_http_handlers` | `usize` | `512` | Prozessweit gleichzeitig ausgeführte HTTP-Handler; HTTPS-Lanes dürfen höchstens die Hälfte mit Long Polls belegen, der Rest bleibt für Session-, Uplink- und Steuerarbeit verfügbar. | | `max_http_handlers` | `usize` | `512` | Prozessweit gleichzeitig ausgeführte HTTP-Handler; HTTPS-Lanes dürfen höchstens die Hälfte mit Long Polls belegen, der Rest bleibt für Session-, Uplink- und Steuerarbeit verfügbar. |
| `max_lane_open_waits_per_session` | `usize` | `16` | Kanonische Cursor-null-Downlink-Polls, die pro Sitzung auf ein konkurrierendes Lane-`OPEN` warten dürfen. | | `max_lane_open_waits_per_session` | `usize` | `16` | Kanonische Cursor-null-Downlink-Polls, die pro Sitzung auf ein konkurrierendes Lane-`OPEN` warten dürfen. |
| `pending_bytes_per_lane` | `usize` | `8388608` | Eingereihte und residente `DATA`-Bytes pro unabhängiger HTTPS- oder WebSocket-Lane. | | `pending_bytes_per_lane` | `usize` | `8388608` | Eingereihte und residente `DATA`-Bytes pro unabhängiger HTTPS- oder WebSocket-Lane. |
@@ -2659,6 +2701,7 @@ Sofern eine Zeile nichts anderes angibt, werden Timeouts in Sekunden angegeben u
| `long_poll_secs` | `u64` | `25` | `✔` | Maximale Dauer eines leeren Downlink-Long-Polls. | | `long_poll_secs` | `u64` | `25` | `✔` | Maximale Dauer eines leeren Downlink-Long-Polls. |
| `bridge_request_secs` | `u64` | `10` | `✔` | Bridge-seitige Deadline eines HTTP-Versuchs bis zum vollständigen Lesen des Response-Bodys; `/down` erhält zusätzlich `long_poll_secs`. Bereich `1..=60`. | | `bridge_request_secs` | `u64` | `10` | `✔` | Bridge-seitige Deadline eines HTTP-Versuchs bis zum vollständigen Lesen des Response-Bodys; `/down` erhält zusätzlich `long_poll_secs`. Bereich `1..=60`. |
| `bridge_retry_secs` | `u64` | `90` | `✔` | Absolutes Bridge-Retry-Fenster einschließlich Versuchen und Backoff; Bereich `1..=300` und nicht kleiner als `bridge_request_secs`. | | `bridge_retry_secs` | `u64` | `90` | `✔` | Absolutes Bridge-Retry-Fenster einschließlich Versuchen und Backoff; Bereich `1..=300` und nicht kleiner als `bridge_request_secs`. |
| `bridge_recovery_secs` | `u64` | `15` | `✔` | Absolutes Recovery-Fenster nach dem Commit für ein weiterlebendes Bridge-Dokument; Bereich `1..=60`, beim Recovery-Start fixiert. |
| `carrier_probe_coalesce_ms` | `u64` | `0` | `✔` | Optionales Bridge-Warten nach `OPEN` auf passendes `DATA`; Millisekunden im Bereich `0..=10`, wobei `0` sofortiges Probing beibehält. | | `carrier_probe_coalesce_ms` | `u64` | `0` | `✔` | Optionales Bridge-Warten nach `OPEN` auf passendes `DATA`; Millisekunden im Bereich `0..=10`, wobei `0` sofortiges Probing beibehält. |
| `lane_open_wait_secs` | `u64` | `2` | `✔` | Wartezeit für einen kanonischen Cursor-null-Downlink, der sein Lane-`OPEN` überholt; höchstens `long_poll_secs`. | | `lane_open_wait_secs` | `u64` | `2` | `✔` | Wartezeit für einen kanonischen Cursor-null-Downlink, der sein Lane-`OPEN` überholt; höchstens `long_poll_secs`. |
| `carrier_health_secs` | `u64` | `30` | `✔` | Beobachtungsintervall nach dem Commit, bevor ein Carrier Learning-Evidenz liefern kann. | | `carrier_health_secs` | `u64` | `30` | `✔` | Beobachtungsintervall nach dem Commit, bevor ein Carrier Learning-Evidenz liefern kann. |
@@ -2672,6 +2715,7 @@ Sofern eine Zeile nichts anderes angibt, werden Timeouts in Sekunden angegeben u
| `bootstrap_lifetime_secs` | `u64` | `120` | `✔` | Lebensdauer ungenutzter Bootstraps und geschlossener Token-Replay-Marker. | | `bootstrap_lifetime_secs` | `u64` | `120` | `✔` | Lebensdauer ungenutzter Bootstraps und geschlossener Token-Replay-Marker. |
| `reconnect_grace_secs` | `u64` | `120` | `✔` | Maximale Carrier-Inaktivität bis zum Schließen der Sitzung. | | `reconnect_grace_secs` | `u64` | `120` | `✔` | Maximale Carrier-Inaktivität bis zum Schließen der Sitzung. |
| `http_idle_secs` | `u64` | `75` | `✔` | Idle-Grenze zwischen HTTP-Austauschvorgängen und bei ausbleibendem Fortschritt eines bereits ausgegebenen Response-Bodys. Explizit begrenzte Request-Body-, Long-Poll-, Decoy- und ausstehende Upgrade-Phasen behalten ihre eigenen Deadlines und werden nicht durch diesen Timer verkürzt. Der Wert wird beim Annehmen der Verbindung fixiert. | | `http_idle_secs` | `u64` | `75` | `✔` | Idle-Grenze zwischen HTTP-Austauschvorgängen und bei ausbleibendem Fortschritt eines bereits ausgegebenen Response-Bodys. Explizit begrenzte Request-Body-, Long-Poll-, Decoy- und ausstehende Upgrade-Phasen behalten ihre eigenen Deadlines und werden nicht durch diesen Timer verkürzt. Der Wert wird beim Annehmen der Verbindung fixiert. |
| `http_overload_timeout_ms` | `u64` | `250` | `✔` | Deadline je Phase in Millisekunden, um bei akzeptierter Überlast auf Kapazität zu warten oder die wiederholbare Antwort zu schreiben; Bereich `1..=60000`. Wait-Timeout und Response-Write erhalten jeweils höchstens ein Phasenbudget. |
| `shutdown_secs` | `u64` | `15` | `✔` | Ein absolutes Budget für das Beenden des Prozesses, das von allen Listener-Acceptoren und Verbindungen sowie WEB-Sitzungs- und Hilfstask-Drains gemeinsam verwendet wird. Der aktive Wert wird beim Start des Shutdowns einmalig erfasst. | | `shutdown_secs` | `u64` | `15` | `✔` | Ein absolutes Budget für das Beenden des Prozesses, das von allen Listener-Acceptoren und Verbindungen sowie WEB-Sitzungs- und Hilfstask-Drains gemeinsam verwendet wird. Der aktive Wert wird beim Start des Shutdowns einmalig erfasst. |
| `decoy_header_secs` | `u64` | `30` | `✔` | Deadline für Verbindung und Response-Head eines HTTP-Decoys. | | `decoy_header_secs` | `u64` | `30` | `✔` | Deadline für Verbindung und Response-Head eines HTTP-Decoys. |
@@ -2680,11 +2724,12 @@ Sofern eine Zeile nichts anderes angibt, werden Timeouts in Sekunden angegeben u
| Schlüssel | Typ | Erforderlich | Hot-Reload | Beschreibung | | Schlüssel | Typ | Erforderlich | Hot-Reload | Beschreibung |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `host` | `String` | ja | `✔` | Eindeutiger, kanonischer ACE-FQDN in Kleinbuchstaben ohne Port, Pfad, Zugangsdaten oder abschließenden Punkt. | | `host` | `String` | ja | `✔` | Eindeutiger, kanonischer ACE-FQDN in Kleinbuchstaben ohne Port, Pfad, Zugangsdaten oder abschließenden Punkt. |
| `base_path` | `String` | nein | `✔` | Exaktes, groß-/kleinschreibungssensitives WEB-Präfix ohne führenden oder abschließenden Schrägstrich; standardmäßig leer. Höchstens 128 ASCII-Bytes in durch Schrägstriche getrennten Segmenten `[A-Za-z0-9][A-Za-z0-9_-]*`. |
| `public_addr` | `SocketAddr` | ja | `✔` | Konkrete öffentliche IP auf Port `443`; wird im Ziel-Tupel des inneren Relays verwendet. | | `public_addr` | `SocketAddr` | ja | `✔` | Konkrete öffentliche IP auf Port `443`; wird im Ziel-Tupel des inneren Relays verwendet. |
| `decoy` | Tabelle | ja | `✔` | Gewöhnlicher Site-Fallback für nicht authentifizierten oder ungültigen Datenverkehr. | | `decoy` | Tabelle | ja | `✔` | Gewöhnlicher Site-Fallback für nicht authentifizierten oder ungültigen Datenverkehr. |
| `profiles` | Tabellen-Array | bei aktiviertem WEB | `✔` | Explizite Benutzer und Client-Secret-Modi für diesen Hostnamen. | | `profiles` | Tabellen-Array | bei aktiviertem WEB | `✔` | Explizite Benutzer und Client-Secret-Modi für diesen Hostnamen. |
Der Hostname wird bei der Validierung normalisiert und muss von Telegram Desktop akzeptiert werden. Ein Bootstrap ist ein Bearer-Token: Client-Adresse und IP-Familie dürfen sich vor der Sitzungserstellung ändern. Ein ungenutzter Bootstrap bleibt über einen Konfigurations-Reload hinweg nur gültig, solange dieselbe Profilidentität aktiv bleibt. Der Hostname wird bei der Validierung normalisiert und muss von Telegram Desktop akzeptiert werden. Ein leerer `base_path` behält die Root-Capability v1 und das bisherige hexadezimale Link-Secret. Ein nicht leerer Pfad verwendet die v2-Host/Pfad-Capability und einen Telegram-Desktop-Pfadlink mit percent-encoded `HOST/BASE` sowie dem base64url-Secret-Marker `0x70`. Das Routing verlangt das exakte Präfix mit abschließendem Schrägstrich und leitet es nie um, normalisiert oder entfernt es. Ein Bootstrap ist ein Bearer-Token: Client-Adresse und IP-Familie dürfen sich vor der Sitzungserstellung ändern. Ein ungenutzter Bootstrap bleibt über einen Konfigurations-Reload hinweg nur gültig, solange dieselbe Profilidentität aktiv bleibt.
# [web.vhosts.decoy] # [web.vhosts.decoy]
@@ -2710,6 +2755,7 @@ Profilgrenzen müssen ungleich null sein und dürfen die zugehörigen globalen G
## WEB-Lebenszyklus und API-Verwaltung ## WEB-Lebenszyklus und API-Verwaltung
- Config-Watcher und Generations-Reload wenden `web.enabled`, Carrier- und Negotiation-Richtlinie, `web.debug`, `web.timeouts`, vhosts, Profile und Decoy-Snapshots ohne Prozessneustart an. Ein einzelner unveränderlicher expandierter Source-Snapshot wird validiert und aktiviert; der Watcher einer Kandidatengeneration startet erst nach deren Aktivierung. Bestehende Sitzungen und laufende Negotiation-Ketten behalten Carrier-Kandidaten, Grenzen, Timeouts und absolute Deadlines ihres Ausgabezeitpunkts; neue Bridge-Sitzungen verwenden genau eine fixierte aktive Generation. - Config-Watcher und Generations-Reload wenden `web.enabled`, Carrier- und Negotiation-Richtlinie, `web.debug`, `web.timeouts`, vhosts, Profile und Decoy-Snapshots ohne Prozessneustart an. Ein einzelner unveränderlicher expandierter Source-Snapshot wird validiert und aktiviert; der Watcher einer Kandidatengeneration startet erst nach deren Aktivierung. Bestehende Sitzungen und laufende Negotiation-Ketten behalten Carrier-Kandidaten, Grenzen, Timeouts und absolute Deadlines ihres Ausgabezeitpunkts; neue Bridge-Sitzungen verwenden genau eine fixierte aktive Generation.
- Eine Änderung von `base_path` ersetzt atomar sowohl die Route für neue Requests als auch die abgeleitete Capability. Geben Sie zuerst neue Links aus und beenden Sie betroffene aktive Sitzungen: etablierte WebSockets und bereits geroutete Austauschvorgänge laufen weiter; spätere Requests an die alte Basis mit einem prozessauthentischen Bootstrap- oder Session-Token erhalten ein lokales, nicht cachebares `404`, während die nun inaktive alte Capability der gewöhnlichen Decoy-Behandlung folgt.
- Bestand und Vertrauensrichtlinie der WEB-Listener unter `server.listeners` sowie alle Werte in `web.limits` sind prozesseigen und erfordern einen Neustart. - Bestand und Vertrauensrichtlinie der WEB-Listener unter `server.listeners` sowie alle Werte in `web.limits` sind prozesseigen und erfordern einen Neustart.
- `GET /v1/config` liefert den vollständigen verfassten `[web]`-Baum außer dem abgeleiteten Snapshot `web.runtime`. `PATCH /v1/config` akzeptiert ein dünn besetztes `web`-Objekt, führt Tabellen tief zusammen, ersetzt Arrays vollständig, validiert den gesamten Kandidaten und meldet `web.limits` bis zum Neustart in `deferred_process_fields`. - `GET /v1/config` liefert den vollständigen verfassten `[web]`-Baum außer dem abgeleiteten Snapshot `web.runtime`. `PATCH /v1/config` akzeptiert ein dünn besetztes `web`-Objekt, führt Tabellen tief zusammen, ersetzt Arrays vollständig, validiert den gesamten Kandidaten und meldet `web.limits` bis zum Neustart in `deferred_process_fields`.
- `GET /v1/runtime/web/status`, `/sessions`, `/sessions/{session_ref}` und `/operations/{operation_id}` stellen begrenzten, nicht geheimen Runtime-Zustand bereit. POST-Steuerungen schließen ausgewählte Sitzungen, löschen Debugdaten oder setzen Carrier-Learning zurück und verlangen die aktuelle zufällige `runtime_instance`. - `GET /v1/runtime/web/status`, `/sessions`, `/sessions/{session_ref}` und `/operations/{operation_id}` stellen begrenzten, nicht geheimen Runtime-Zustand bereit. POST-Steuerungen schließen ausgewählte Sitzungen, löschen Debugdaten oder setzen Carrier-Learning zurück und verlangen die aktuelle zufällige `runtime_instance`.
@@ -2837,8 +2883,10 @@ Profilgrenzen müssen ungleich null sein und dürfen die zugehörigen globalen G
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` | | [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` |
| [`tls_fetch`](#tls_fetch) | `Table` | integrierte Standardeinstellungen | `✘` | | [`tls_fetch`](#tls_fetch) | `Table` | integrierte Standardeinstellungen | `✘` |
| [`mask`](#mask) | `bool` | `true` | `✘` | | [`mask`](#mask) | `bool` | `true` | `✘` |
| [`mask_dynamic`](#mask_dynamic) | `bool` | `true` | `✘` |
| [`mask_host`](#mask_host) | `String` | — | `✘` | | [`mask_host`](#mask_host) | `String` | — | `✘` |
| [`mask_port`](#mask_port) | `u16` | `443` | `✘` | | [`mask_port`](#mask_port) | `u16` | `443` | `✘` |
| [`exclusive_mask`](#exclusive_mask) | `Map<String, String>` | `{}` | `✘` |
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` | | [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` |
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` | | [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` |
| [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` | | [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` |
@@ -2926,11 +2974,20 @@ Profilgrenzen müssen ungleich null sein und dürfen die zugehörigen globalen G
[censorship] [censorship]
mask = true mask = true
``` ```
## mask_dynamic
- **Einschränkungen / Validierung**: `bool`.
- **Beschreibung**: Wenn weder `mask_host` noch `mask_unix_sock` gesetzt ist, wird eine passende ClientHello-SNI aus `tls_domain`/`tls_domains` als TCP-Mask-Ziel verwendet; ohne Treffer wird auf die primäre `tls_domain` zurückgegriffen. Ein passender `exclusive_mask`-Eintrag hat immer Vorrang vor regulären Zielen.
- **Beispiel**:
```toml
[censorship]
mask_dynamic = true
```
## mask_host ## mask_host
- **Einschränkungen / Validierung**: `String` (optional). - **Einschränkungen / Validierung**: `String` (optional).
- Wenn `mask_unix_sock` gesetzt ist, muss `mask_host` ausgelassen werden (mutually exclusive). - Wenn `mask_unix_sock` gesetzt ist, muss `mask_host` ausgelassen werden (mutually exclusive).
- Wenn weder `mask_host` noch `mask_unix_sock` gesetzt ist, verwendet Telemt standardmäßig `tls_domain` als `mask_host`. - Wenn weder `mask_host` noch `mask_unix_sock` gesetzt ist, darf `mask_dynamic` eine passende konfigurierte SNI wählen; andernfalls verwendet Telemt `tls_domain`.
- **Beschreibung**: Upstream-Mask-Host für das TLS-Fronting-Relay. - **Beschreibung**: Expliziter Upstream-Mask-Host für das TLS-Fronting-Relay. Wenn gesetzt, deaktiviert er die dynamische SNI-Zielwahl mit Ausnahme von `exclusive_mask`-Overrides.
- **Beispiel**: - **Beispiel**:
```toml ```toml
@@ -3444,8 +3501,9 @@ Wenn Backend oder Netzwerk stark bandbreitenbeschränkt sind, reduzieren Sie zue
user_max_tcp_conns_global_each = 200 user_max_tcp_conns_global_each = 200
[access.user_max_tcp_conns] [access.user_max_tcp_conns]
alice = 500 # uses 500, not the global cap # Alice uses 500 rather than the global cap.
# bob hat keinen Eintrag → verwendet 200 alice = 500
# Bob has no entry and therefore uses 200.
``` ```
## user_expirations ## user_expirations
- **Einschränkungen / Validierung**: `Map<String, DateTime<Utc>>`. Jeder Wert muss eine gültige RFC3339/ISO-8601-Datumszeit sein. - **Einschränkungen / Validierung**: `Map<String, DateTime<Utc>>`. Jeder Wert muss eine gültige RFC3339/ISO-8601-Datumszeit sein.
@@ -3463,7 +3521,8 @@ Wenn Backend oder Netzwerk stark bandbreitenbeschränkt sind, reduzieren Sie zue
```toml ```toml
[access.user_data_quota] [access.user_data_quota]
alice = 1073741824 # 1 GiB # Alice receives a 1 GiB quota.
alice = 1073741824
``` ```
## user_max_unique_ips ## user_max_unique_ips
- **Einschränkungen / Validierung**: `Map<String, usize>`. - **Einschränkungen / Validierung**: `Map<String, usize>`.
@@ -3545,7 +3604,7 @@ Wenn Backend oder Netzwerk stark bandbreitenbeschränkt sind, reduzieren Sie zue
## user_rate_limits ## user_rate_limits
- **Einschränkungen / Validierung**: Tabelle `username -> { up_bps, down_bps }`. Mindestens eine Richtung muss ungleich Null sein. - **Einschränkungen / Validierung**: Tabelle `username -> { up_bps, down_bps }`. Jede Richtung muss in `0..=100000000000` liegen; `0` bedeutet für diese Richtung unbegrenzt, und mindestens eine Richtung muss ungleich Null sein.
- **Beschreibung**: Bandbreitenobergrenzen pro User in Bits/Sekunde für Upload (`up_bps`) und Download (`down_bps`). - **Beschreibung**: Bandbreitenobergrenzen pro User in Bits/Sekunde für Upload (`up_bps`) und Download (`down_bps`).
- **Beispiel**: - **Beispiel**:
@@ -3554,7 +3613,7 @@ Wenn Backend oder Netzwerk stark bandbreitenbeschränkt sind, reduzieren Sie zue
alice = { up_bps = 1048576, down_bps = 2097152 } alice = { up_bps = 1048576, down_bps = 2097152 }
``` ```
## cidr_rate_limits ## cidr_rate_limits
- **Einschränkungen / Validierung**: Tabelle `CIDR oder Auto-Template -> { up_bps, down_bps }`. Explizite CIDR-Schlüssel müssen als `IpNetwork` parsbar sein; Auto-Template-Schlüssel müssen `*4/N` (`N=0..32`), `*6/N` (`N=0..128`) oder `*/N` (`N=0..32`) verwenden. Mindestens eine Richtung muss ungleich Null sein. Doppelte normalisierte Auto-Templates werden abgelehnt. - **Einschränkungen / Validierung**: Tabelle `CIDR oder Auto-Template -> { up_bps, down_bps }`. Jede Richtung muss in `0..=100000000000` liegen; `0` bedeutet für diese Richtung unbegrenzt, und mindestens eine Richtung muss ungleich Null sein. Explizite CIDR-Schlüssel müssen als `IpNetwork` parsbar sein; Auto-Template-Schlüssel müssen `*4/N` (`N=0..32`), `*6/N` (`N=0..128`) oder `*/N` (`N=0..32`) verwenden. Doppelte normalisierte Auto-Templates werden abgelehnt.
- **Beschreibung**: Source-Subnetz-Bandbreitenlimits, die zusätzlich zu Per-User-Limits greifen. Explizite CIDR-Regeln verwenden Longest-Prefix-Wins und haben Vorrang vor Auto-Templates. Auto-Templates erzeugen Buckets lazy pro passendem Source-Subnetz: `*4/N` für IPv4, `*6/N` für IPv6 und `*/N` als Dual-Stack-Shorthand, bei dem IPv4 `/N` und IPv6 `/(N * 4)` nutzt. - **Beschreibung**: Source-Subnetz-Bandbreitenlimits, die zusätzlich zu Per-User-Limits greifen. Explizite CIDR-Regeln verwenden Longest-Prefix-Wins und haben Vorrang vor Auto-Templates. Auto-Templates erzeugen Buckets lazy pro passendem Source-Subnetz: `*4/N` für IPv4, `*6/N` für IPv6 und `*/N` als Dual-Stack-Shorthand, bei dem IPv4 `/N` und IPv6 `/(N * 4)` nutzt.
- **Beispiel**: - **Beispiel**:
@@ -3683,7 +3742,8 @@ Wenn Backend oder Netzwerk stark bandbreitenbeschränkt sind, reduzieren Sie zue
[[upstreams]] [[upstreams]]
type = "socks5" type = "socks5"
address = "203.0.113.10:1080" address = "203.0.113.10:1080"
interface = "192.0.2.10" # explicit local bind IP # Use an explicit local bind IP.
interface = "192.0.2.10"
``` ```
## bind_addresses ## bind_addresses
- **Einschränkungen / Validierung**: `String[]` (optional). Gilt nur für `type = "direct"`. - **Einschränkungen / Validierung**: `String[]` (optional). Gilt nur für `type = "direct"`.
+66 -25
View File
@@ -10,7 +10,7 @@ This document lists all configuration keys accepted by `config.toml`.
> >
> The configuration parameters detailed in this document are intended for advanced users and fine-tuning purposes. Modifying these settings without a clear understanding of their function may lead to application instability or other unexpected behavior. Please proceed with caution and at your own risk. > The configuration parameters detailed in this document are intended for advanced users and fine-tuning purposes. Modifying these settings without a clear understanding of their function may lead to application instability or other unexpected behavior. Please proceed with caution and at your own risk.
> `Hot-Reload` marks whether a changed value is applied by the config watcher without restarting the process; `✘` means restart is required for runtime effect. > `Hot-Reload` marks whether a changed value is applied directly by the config watcher. `✘` means the watcher does not apply it; depending on the field, full effect requires an in-process runtime-generation reload or a process restart.
# Table of contents # Table of contents
- [Top-level keys](#top-level-keys) - [Top-level keys](#top-level-keys)
@@ -204,6 +204,7 @@ This document lists all configuration keys accepted by `config.toml`.
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` | | [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` |
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` | | [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` |
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` | | [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` |
| [`me_writer_byte_budget_bytes`](#me_writer_byte_budget_bytes) | `usize` | `33570816` | `✘` |
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` | | [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` |
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` | | [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` |
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` | | [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` |
@@ -216,6 +217,7 @@ This document lists all configuration keys accepted by `config.toml`.
| [`me_d2c_frame_buf_shrink_threshold_bytes`](#me_d2c_frame_buf_shrink_threshold_bytes) | `usize` | `262144` | `✔` | | [`me_d2c_frame_buf_shrink_threshold_bytes`](#me_d2c_frame_buf_shrink_threshold_bytes) | `usize` | `262144` | `✔` |
| [`direct_relay_copy_buf_c2s_bytes`](#direct_relay_copy_buf_c2s_bytes) | `usize` | `65536` | `✔` | | [`direct_relay_copy_buf_c2s_bytes`](#direct_relay_copy_buf_c2s_bytes) | `usize` | `65536` | `✔` |
| [`direct_relay_copy_buf_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` | | [`direct_relay_copy_buf_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` |
| [`direct_relay_buffer_budget_max_bytes`](#direct_relay_buffer_budget_max_bytes) | `usize` | `0` | `✘` |
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` | | [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` |
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` | | [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` |
| [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` | | [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` |
@@ -301,7 +303,7 @@ This document lists all configuration keys accepted by `config.toml`.
| [`me_pool_drain_soft_evict_per_writer`](#me_pool_drain_soft_evict_per_writer) | `u8` | `2` | `✘` | | [`me_pool_drain_soft_evict_per_writer`](#me_pool_drain_soft_evict_per_writer) | `u8` | `2` | `✘` |
| [`me_pool_drain_soft_evict_budget_per_core`](#me_pool_drain_soft_evict_budget_per_core) | `u16` | `16` | `✘` | | [`me_pool_drain_soft_evict_budget_per_core`](#me_pool_drain_soft_evict_budget_per_core) | `u16` | `16` | `✘` |
| [`me_pool_drain_soft_evict_cooldown_ms`](#me_pool_drain_soft_evict_cooldown_ms) | `u64` | `1000` | `✘` | | [`me_pool_drain_soft_evict_cooldown_ms`](#me_pool_drain_soft_evict_cooldown_ms) | `u64` | `1000` | `✘` |
| [`me_bind_stale_mode`](#me_bind_stale_mode) | `"never"`, `"ttl"`, or `"always"` | `"ttl"` | `✔` | | [`me_bind_stale_mode`](#me_bind_stale_mode) | `"never"`, `"ttl"`, or `"always"` | `"never"` | `✔` |
| [`me_bind_stale_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` | | [`me_bind_stale_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` |
| [`me_pool_min_fresh_ratio`](#me_pool_min_fresh_ratio) | `f32` | `0.8` | `✔` | | [`me_pool_min_fresh_ratio`](#me_pool_min_fresh_ratio) | `f32` | `0.8` | `✔` |
| [`me_reinit_drain_timeout_secs`](#me_reinit_drain_timeout_secs) | `u64` | `90` | `✔` | | [`me_reinit_drain_timeout_secs`](#me_reinit_drain_timeout_secs) | `u64` | `90` | `✔` |
@@ -347,6 +349,8 @@ This document lists all configuration keys accepted by `config.toml`.
[general] [general]
config_strict = true config_strict = true
``` ```
- **Known limitation**: In this revision, `config_strict = true` rejects the otherwise supported `access.user_source_deny` and `[[upstreams]].prefer` keys. Keep strict mode disabled when either key is present.
## prefer_ipv6 ## prefer_ipv6
- **Constraints / validation**: Deprecated. Use `network.prefer`. - **Constraints / validation**: Deprecated. Use `network.prefer`.
- **Description**: Deprecated legacy IPv6 preference flag migrated to `network.prefer`. - **Description**: Deprecated legacy IPv6 preference flag migrated to `network.prefer`.
@@ -483,8 +487,8 @@ This document lists all configuration keys accepted by `config.toml`.
stun_nat_probe_concurrency = 8 stun_nat_probe_concurrency = 8
``` ```
## middle_proxy_pool_size ## middle_proxy_pool_size
- **Constraints / validation**: `usize`. Effective value is `max(value, 1)` at runtime (so `0` behaves as `1`). - **Constraints / validation**: `usize`. The value passed to ME initialization is normalized as `max(value, 1)`.
- **Description**: Target size of active ME writer pool. - **Description**: Non-enforcing compatibility input currently emitted in the ME initialization log. Active writer targets are derived from the DC-family floor policy, not this value.
- **Example**: - **Example**:
```toml ```toml
@@ -575,7 +579,7 @@ This document lists all configuration keys accepted by `config.toml`.
rpc_proxy_req_every = 0 rpc_proxy_req_every = 0
``` ```
## me_writer_cmd_channel_capacity ## me_writer_cmd_channel_capacity
- **Constraints / validation**: Must be `> 0`. - **Constraints / validation**: Must be within `1..=16384`.
- **Description**: Capacity of per-writer command channel. - **Description**: Capacity of per-writer command channel.
- **Example**: - **Example**:
@@ -583,8 +587,17 @@ This document lists all configuration keys accepted by `config.toml`.
[general] [general]
me_writer_cmd_channel_capacity = 4096 me_writer_cmd_channel_capacity = 4096
``` ```
## me_writer_byte_budget_bytes
- **Constraints / validation**: Must be a multiple of `16384` within the dynamic minimum and `268435456`. The minimum is `2 * general.max_client_frame + 256`, rounded up to `16384`; with the default frame size it is `33570816`.
- **Description**: Resident byte budget for each ME writer data queue. The file watcher does not rebuild existing writers for this field; it takes effect when a new ME/runtime generation is built through the API or after restart.
- **Example**:
```toml
[general]
me_writer_byte_budget_bytes = 33570816
```
## me_route_channel_capacity ## me_route_channel_capacity
- **Constraints / validation**: Must be `> 0`. - **Constraints / validation**: Must be within `1..=8192`.
- **Description**: Capacity of per-connection ME response route channel. - **Description**: Capacity of per-connection ME response route channel.
- **Example**: - **Example**:
@@ -593,7 +606,7 @@ This document lists all configuration keys accepted by `config.toml`.
me_route_channel_capacity = 768 me_route_channel_capacity = 768
``` ```
## me_c2me_channel_capacity ## me_c2me_channel_capacity
- **Constraints / validation**: Must be `> 0`. - **Constraints / validation**: Must be within `1..=8192`.
- **Description**: Capacity of per-client command queue (client reader -> ME sender). - **Description**: Capacity of per-client command queue (client reader -> ME sender).
- **Example**: - **Example**:
@@ -691,6 +704,15 @@ This document lists all configuration keys accepted by `config.toml`.
[general] [general]
direct_relay_copy_buf_s2c_bytes = 262144 direct_relay_copy_buf_s2c_bytes = 262144
``` ```
## direct_relay_buffer_budget_max_bytes
- **Constraints / validation**: `0`, or a multiple of `4096` within `16777216..=2147483648`.
- **Description**: Process-wide hard ceiling for Direct relay copy buffers. `0` derives the ceiling at process startup from cgroup or host memory limits. This field is process-owned and restart-deferred.
- **Example**:
```toml
[general]
direct_relay_buffer_budget_max_bytes = 0
```
## crypto_pending_buffer ## crypto_pending_buffer
- **Constraints / validation**: `usize` (bytes). - **Constraints / validation**: `usize` (bytes).
- **Description**: Max pending ciphertext buffer per client writer (bytes). - **Description**: Max pending ciphertext buffer per client writer (bytes).
@@ -701,7 +723,7 @@ This document lists all configuration keys accepted by `config.toml`.
crypto_pending_buffer = 262144 crypto_pending_buffer = 262144
``` ```
## max_client_frame ## max_client_frame
- **Constraints / validation**: `usize` (bytes). - **Constraints / validation**: Must be within `4096..=16777216` (bytes).
- **Description**: Maximum allowed client MTProto frame size (bytes). - **Description**: Maximum allowed client MTProto frame size (bytes).
- **Example**: - **Example**:
@@ -1214,7 +1236,7 @@ This document lists all configuration keys accepted by `config.toml`.
me_route_hybrid_max_wait_ms = 3000 me_route_hybrid_max_wait_ms = 3000
``` ```
## me_route_blocking_send_timeout_ms ## me_route_blocking_send_timeout_ms
- **Constraints / validation**: Must be within `0..=5000` (milliseconds). `0` keeps legacy unbounded wait behavior. - **Constraints / validation**: Must be within `1..=5000` (milliseconds).
- **Description**: Maximum wait for blocking route-channel send fallback. - **Description**: Maximum wait for blocking route-channel send fallback.
- **Example**: - **Example**:
@@ -1397,7 +1419,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
## me_pool_drain_ttl_secs ## me_pool_drain_ttl_secs
- **Constraints / validation**: `u64` (seconds). `0` disables the drain-TTL window (and suppresses drain-TTL warnings for non-empty draining writers). - **Constraints / validation**: `u64` (seconds). `0` disables the drain-TTL window (and suppresses drain-TTL warnings for non-empty draining writers).
- **Description**: Drain-TTL time window for stale ME writers after endpoint map changes. During the TTL, stale writers may be used only as fallback for new bindings (depending on bind policy). - **Description**: Age threshold for prolonged-drain warnings after endpoint map changes and the lower bound used when normalizing the force-close timeout. Stale-bind eligibility is controlled separately by `me_bind_stale_mode` and `me_bind_stale_ttl_secs`.
- **Example**: - **Example**:
```toml ```toml
@@ -1477,7 +1499,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
## me_bind_stale_mode ## me_bind_stale_mode
- **Constraints / validation**: `"never"`, `"ttl"`, or `"always"`. - **Constraints / validation**: `"never"`, `"ttl"`, or `"always"`.
- **Description**: Policy for new binds on stale draining writers. - **Description**: Policy for new binds on stale draining writers in uncovered DC-family groups. The default `never` requires complete group coverage before a partial hardswap can commit; `ttl` and `always` permit policy-bounded fallback.
- **Example**: - **Example**:
```toml ```toml
@@ -1487,7 +1509,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
## me_bind_stale_ttl_secs ## me_bind_stale_ttl_secs
- **Constraints / validation**: `u64`. - **Constraints / validation**: `u64`.
- **Description**: TTL for stale bind allowance when stale mode is `ttl`. - **Description**: TTL for stale bind allowance when stale mode is `ttl`; `0` disables TTL expiry for eligible draining writers.
- **Example**: - **Example**:
```toml ```toml
@@ -1497,7 +1519,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
## me_pool_min_fresh_ratio ## me_pool_min_fresh_ratio
- **Constraints / validation**: Must be within `[0.0, 1.0]`. - **Constraints / validation**: Must be within `[0.0, 1.0]`.
- **Description**: Minimum fresh desired-DC coverage ratio before stale writers are drained. - **Description**: Minimum fresh DC-family coverage ratio required at generation commit. Missing groups still block commit under `me_bind_stale_mode = "never"` even when this ratio is satisfied.
- **Example**: - **Example**:
```toml ```toml
@@ -1506,8 +1528,8 @@ This document lists all configuration keys accepted by `config.toml`.
me_pool_min_fresh_ratio = 0.9 me_pool_min_fresh_ratio = 0.9
``` ```
## me_reinit_drain_timeout_secs ## me_reinit_drain_timeout_secs
- **Constraints / validation**: `u64`. `0` uses the runtime safety fallback force-close timeout. If `> 0` and `< me_pool_drain_ttl_secs`, runtime bumps it to TTL. - **Constraints / validation**: `u64`. `0` first selects the 300-second runtime safety fallback; the effective timeout is then raised to at least `me_pool_drain_ttl_secs`.
- **Description**: Force-close timeout for draining stale writers. When set to `0`, the effective timeout is the runtime safety fallback (300 seconds). - **Description**: Force-close timeout for draining stale writers. The effective value is the greater of the configured non-zero value (or 300 seconds for `0`) and the drain TTL.
- **Example**: - **Example**:
```toml ```toml
@@ -1559,7 +1581,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
## me_reinit_trigger_channel ## me_reinit_trigger_channel
- **Constraints / validation**: Must be within `[1, 4096]`. - **Constraints / validation**: Must be within `[1, 4096]`.
- **Description**: Trigger queue capacity for reinit scheduler. - **Description**: Trigger queue capacity for the reinit scheduler. A new runtime generation constructs its channel from this value; the file watcher alone does not resize the active channel.
- **Example**: - **Example**:
```toml ```toml
@@ -2596,6 +2618,7 @@ This hot-reloadable table controls the process-owned server-side WEB debug recor
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `enabled` | `bool` | `false` | Enables WEB HTTP, WebSocket-message, frame, and lifecycle debug records. | | `enabled` | `bool` | `false` | Enables WEB HTTP, WebSocket-message, frame, and lifecycle debug records. |
| `capture_lifecycle` | `bool` | `true` | Records typed bridge, session, stream, handshake, relay, and close events. | | `capture_lifecycle` | `bool` | `true` | Records typed bridge, session, stream, handshake, relay, and close events. |
| `sideband` | `bool` | `false` | Enables generated-bridge lifecycle diagnostics; effective only when `enabled` and `capture_lifecycle` are also true. |
| `capture_headers` | `bool` | `true` | Retains header names and only allowlisted non-credential values. | | `capture_headers` | `bool` | `true` | Retains header names and only allowlisted non-credential values. |
| `capture_timings` | `bool` | `true` | Retains request-body, response-ready, response-body, and WebSocket message-processing timing points. | | `capture_timings` | `bool` | `true` | Retains request-body, response-ready, response-body, and WebSocket message-processing timing points. |
| `capture_frames` | `bool` | `true` | Parses bounded carrier bodies into frame type, stream ID, length, WINDOW, and error metadata without retaining frame payload separately. | | `capture_frames` | `bool` | `true` | Parses bounded carrier bodies into frame type, stream ID, length, WINDOW, and error metadata without retaining frame payload separately. |
@@ -2607,6 +2630,8 @@ This hot-reloadable table controls the process-owned server-side WEB debug recor
Changing `enabled` or any capture field clears retained records and rejects commits started under the previous policy epoch. Changing only the default or maximum observation window preserves compatible retained records. `full` retains a complete recognized carrier body only up to `web.limits.max_body_bytes`; decoy bodies always remain prefix-bounded. A prefix that depends on a simultaneously increased restart-only capacity is deferred with `web.debug` until restart. URI queries are never retained, credential header values are omitted, body copies are scrubbed for known WEB capabilities and bearer tokens, and profile keys are represented only by a domain-separated 16-hex fingerprint. Changing `enabled` or any capture field clears retained records and rejects commits started under the previous policy epoch. Changing only the default or maximum observation window preserves compatible retained records. `full` retains a complete recognized carrier body only up to `web.limits.max_body_bytes`; decoy bodies always remain prefix-bounded. A prefix that depends on a simultaneously increased restart-only capacity is deferred with `web.debug` until restart. URI queries are never retained, credential header values are omitted, body copies are scrubbed for known WEB capabilities and bearer tokens, and profile keys are represented only by a domain-separated 16-hex fingerprint.
When `enabled`, `sideband`, and `capture_lifecycle` are all true, newly generated bridge pages send bounded one-shot lifecycle events to the exact configured base plus `api/v1/diagnostic`. The route is internal to Telemt and does not expose a public control API. Existing bridge documents do not acquire sideband behavior after reload.
Authenticated JSON control may clear the ring explicitly with `POST /v1/runtime/web/debug/clear`; the required process `runtime_instance` fences stale controllers, the returned epoch fences in-flight writers, and `leased_bytes` reports memory still owned by already rendered snapshots. Authenticated JSON control may clear the ring explicitly with `POST /v1/runtime/web/debug/clear`; the required process `runtime_instance` fences stale controllers, the returned epoch fences in-flight writers, and `leased_bytes` reports memory still owned by already rendered snapshots.
# [web.limits] # [web.limits]
@@ -2699,11 +2724,12 @@ Unless a row states otherwise, timeouts are measured in seconds and must be with
| Key | Type | Required | Hot-Reload | Description | | Key | Type | Required | Hot-Reload | Description |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `host` | `String` | yes | `✔` | Unique, canonical lowercase ACE FQDN without port, path, credentials, or trailing dot. | | `host` | `String` | yes | `✔` | Unique, canonical lowercase ACE FQDN without port, path, credentials, or trailing dot. |
| `base_path` | `String` | no | `✔` | Exact case-sensitive WEB prefix without leading or trailing slash; empty by default. At most 128 ASCII bytes in slash-separated `[A-Za-z0-9][A-Za-z0-9_-]*` segments. |
| `public_addr` | `SocketAddr` | yes | `✔` | Concrete public IP on port `443`; used in the inner relay destination tuple. | | `public_addr` | `SocketAddr` | yes | `✔` | Concrete public IP on port `443`; used in the inner relay destination tuple. |
| `decoy` | table | yes | `✔` | Ordinary-site fallback for unauthenticated or invalid traffic. | | `decoy` | table | yes | `✔` | Ordinary-site fallback for unauthenticated or invalid traffic. |
| `profiles` | array of tables | when enabled | `✔` | Explicit users and client secret modes exposed by this hostname. | | `profiles` | array of tables | when enabled | `✔` | Explicit users and client secret modes exposed by this hostname. |
The hostname must be accepted by Telegram Desktop and is normalized during validation. A bootstrap is a bearer credential: its client address and address family may change before session creation. An unused bootstrap remains valid across a configuration reload only while the same profile identity is still active. The hostname must be accepted by Telegram Desktop and is normalized during validation. An empty `base_path` keeps the root v1 capability and legacy hexadecimal link secret. A non-empty path uses the v2 host/path capability and a Telegram Desktop path link with percent-encoded `HOST/BASE` plus the `0x70` base64url secret marker. Routing requires the exact slash-terminated prefix and never redirects, normalizes, or strips it. A bootstrap is a bearer credential: its client address and address family may change before session creation. An unused bootstrap remains valid across a configuration reload only while the same profile identity is still active.
# [web.vhosts.decoy] # [web.vhosts.decoy]
@@ -2729,6 +2755,7 @@ Profile limits must be non-zero and no greater than their corresponding global l
## WEB lifecycle and API management ## WEB lifecycle and API management
- The config watcher and generation reload apply `web.enabled`, carrier and negotiation policy, `web.debug`, `web.timeouts`, vhosts, profiles, and decoy snapshots without a process restart. One immutable expanded source snapshot is validated and activated; a candidate generation's watcher starts only after that generation becomes active. Existing sessions and in-flight negotiation chains keep their issuance-time carrier candidates, limits, timeouts, and absolute deadlines; newly issued bridge sessions use one pinned active generation. - The config watcher and generation reload apply `web.enabled`, carrier and negotiation policy, `web.debug`, `web.timeouts`, vhosts, profiles, and decoy snapshots without a process restart. One immutable expanded source snapshot is validated and activated; a candidate generation's watcher starts only after that generation becomes active. Existing sessions and in-flight negotiation chains keep their issuance-time carrier candidates, limits, timeouts, and absolute deadlines; newly issued bridge sessions use one pinned active generation.
- Changing `base_path` atomically replaces both the new-request route and derived capability. Reissue links and drain affected live sessions first: established WebSockets and already routed exchanges continue; later old-base requests carrying a process-authentic bootstrap or session token receive a local no-store `404`, while the now-inactive old capability follows ordinary decoy handling.
- WEB listener inventory and trust policy under `server.listeners`, and every `web.limits` value, are process-owned and restart-required. - WEB listener inventory and trust policy under `server.listeners`, and every `web.limits` value, are process-owned and restart-required.
- `GET /v1/config` returns the complete authored `[web]` tree except the derived `web.runtime` snapshot. `PATCH /v1/config` accepts a sparse `web` object, deep-merges tables, replaces arrays wholesale, validates the complete candidate, and reports `web.limits` in `deferred_process_fields` until restart. - `GET /v1/config` returns the complete authored `[web]` tree except the derived `web.runtime` snapshot. `PATCH /v1/config` accepts a sparse `web` object, deep-merges tables, replaces arrays wholesale, validates the complete candidate, and reports `web.limits` in `deferred_process_fields` until restart.
- `GET /v1/runtime/web/status`, `/sessions`, `/sessions/{session_ref}`, and `/operations/{operation_id}` expose bounded non-secret runtime state. POST controls close selected sessions, clear debug data, or reset carrier learning and require the current random `runtime_instance`. - `GET /v1/runtime/web/status`, `/sessions`, `/sessions/{session_ref}`, and `/operations/{operation_id}` expose bounded non-secret runtime state. POST controls close selected sessions, clear debug data, or reset carrier learning and require the current random `runtime_instance`.
@@ -2856,8 +2883,10 @@ Profile limits must be non-zero and no greater than their corresponding global l
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` | | [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` |
| [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults | `✘` | | [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults | `✘` |
| [`mask`](#mask) | `bool` | `true` | `✘` | | [`mask`](#mask) | `bool` | `true` | `✘` |
| [`mask_dynamic`](#mask_dynamic) | `bool` | `true` | `✘` |
| [`mask_host`](#mask_host) | `String` | — | `✘` | | [`mask_host`](#mask_host) | `String` | — | `✘` |
| [`mask_port`](#mask_port) | `u16` | `443` | `✘` | | [`mask_port`](#mask_port) | `u16` | `443` | `✘` |
| [`exclusive_mask`](#exclusive_mask) | `Map<String, String>` | `{}` | `✘` |
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` | | [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` |
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` | | [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` |
| [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` | | [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` |
@@ -2945,11 +2974,20 @@ Profile limits must be non-zero and no greater than their corresponding global l
[censorship] [censorship]
mask = true mask = true
``` ```
## mask_dynamic
- **Constraints / validation**: `bool`.
- **Description**: When neither `mask_host` nor `mask_unix_sock` is configured, use a matching ClientHello SNI from `tls_domain`/`tls_domains` as the TCP mask target; if none matches, fall back to the primary `tls_domain`. A matching `exclusive_mask` entry always takes precedence over ordinary targets.
- **Example**:
```toml
[censorship]
mask_dynamic = true
```
## mask_host ## mask_host
- **Constraints / validation**: `String` (optional). - **Constraints / validation**: `String` (optional).
- If `mask_unix_sock` is set, `mask_host` must be omitted (mutually exclusive). - If `mask_unix_sock` is set, `mask_host` must be omitted (mutually exclusive).
- If `mask_host` is not set and `mask_unix_sock` is not set, Telemt defaults `mask_host` to `tls_domain`. - If neither `mask_host` nor `mask_unix_sock` is set, `mask_dynamic` may select a matching configured SNI; otherwise Telemt falls back to `tls_domain`.
- **Description**: Upstream mask host for TLS fronting relay. - **Description**: Explicit upstream mask host for TLS fronting relay. When present, it disables dynamic SNI target selection except for `exclusive_mask` overrides.
- **Example**: - **Example**:
```toml ```toml
@@ -3463,8 +3501,9 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
user_max_tcp_conns_global_each = 200 user_max_tcp_conns_global_each = 200
[access.user_max_tcp_conns] [access.user_max_tcp_conns]
alice = 500 # uses 500, not the global cap # Alice uses 500 rather than the global cap.
# bob has no entry → uses 200 alice = 500
# Bob has no entry and therefore uses 200.
``` ```
## user_expirations ## user_expirations
- **Constraints / validation**: `Map<String, DateTime<Utc>>`. Each value must be a valid RFC3339 / ISO-8601 datetime. - **Constraints / validation**: `Map<String, DateTime<Utc>>`. Each value must be a valid RFC3339 / ISO-8601 datetime.
@@ -3482,7 +3521,8 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
```toml ```toml
[access.user_data_quota] [access.user_data_quota]
alice = 1073741824 # 1 GiB # Alice receives a 1 GiB quota.
alice = 1073741824
``` ```
## user_max_unique_ips ## user_max_unique_ips
- **Constraints / validation**: `Map<String, usize>`. - **Constraints / validation**: `Map<String, usize>`.
@@ -3564,7 +3604,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
## user_rate_limits ## user_rate_limits
- **Constraints / validation**: Table `username -> { up_bps, down_bps }`. At least one direction must be non-zero. - **Constraints / validation**: Table `username -> { up_bps, down_bps }`. Each direction must be within `0..=100000000000`; `0` means unlimited for that direction, and at least one direction must be non-zero.
- **Description**: Per-user bandwidth caps in bits/sec for upload (`up_bps`) and download (`down_bps`). - **Description**: Per-user bandwidth caps in bits/sec for upload (`up_bps`) and download (`down_bps`).
- **Example**: - **Example**:
@@ -3573,7 +3613,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
alice = { up_bps = 1048576, down_bps = 2097152 } alice = { up_bps = 1048576, down_bps = 2097152 }
``` ```
## cidr_rate_limits ## cidr_rate_limits
- **Constraints / validation**: Table `CIDR or auto-template -> { up_bps, down_bps }`. Explicit CIDR keys must parse as `IpNetwork`; auto-template keys must be `*4/N` (`N=0..32`), `*6/N` (`N=0..128`), or `*/N` (`N=0..32`). At least one direction must be non-zero. Duplicate normalized auto-templates are rejected. - **Constraints / validation**: Table `CIDR or auto-template -> { up_bps, down_bps }`. Each direction must be within `0..=100000000000`; `0` means unlimited for that direction, and at least one direction must be non-zero. Explicit CIDR keys must parse as `IpNetwork`; auto-template keys must be `*4/N` (`N=0..32`), `*6/N` (`N=0..128`), or `*/N` (`N=0..32`). Duplicate normalized auto-templates are rejected.
- **Description**: Source-subnet bandwidth caps applied alongside per-user limits. Explicit CIDR rules use longest-prefix-wins and take priority over auto-templates. Auto-templates create buckets lazily per matched source subnet: `*4/N` for IPv4, `*6/N` for IPv6, and `*/N` as a dual-stack shorthand where IPv4 uses `/N` and IPv6 uses `/(N * 4)`. - **Description**: Source-subnet bandwidth caps applied alongside per-user limits. Explicit CIDR rules use longest-prefix-wins and take priority over auto-templates. Auto-templates create buckets lazily per matched source subnet: `*4/N` for IPv4, `*6/N` for IPv6, and `*/N` as a dual-stack shorthand where IPv4 uses `/N` and IPv6 uses `/(N * 4)`.
- **Example**: - **Example**:
@@ -3702,7 +3742,8 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
[[upstreams]] [[upstreams]]
type = "socks5" type = "socks5"
address = "203.0.113.10:1080" address = "203.0.113.10:1080"
interface = "192.0.2.10" # explicit local bind IP # Use an explicit local bind IP.
interface = "192.0.2.10"
``` ```
## bind_addresses ## bind_addresses
- **Constraints / validation**: `String[]` (optional). Applies only to `type = "direct"`. - **Constraints / validation**: `String[]` (optional). Applies only to `type = "direct"`.
+186 -35
View File
@@ -10,10 +10,11 @@
> >
> Параметры конфигурации, подробно описанные в этом документе, предназначены для опытных пользователей и для целей тонкой настройки. Изменение этих параметров без четкого понимания их функции может привести к нестабильности приложения или другому неожиданному поведению. Пожалуйста, действуйте осторожно и на свой страх и риск. > Параметры конфигурации, подробно описанные в этом документе, предназначены для опытных пользователей и для целей тонкой настройки. Изменение этих параметров без четкого понимания их функции может привести к нестабильности приложения или другому неожиданному поведению. Пожалуйста, действуйте осторожно и на свой страх и риск.
> `Hot-Reload` показывает, применяет ли config watcher изменение без перезапуска процесса; `✘` означает, что для runtime-эффекта нужен перезапуск. > `Hot-Reload` показывает, применяет ли config watcher изменение напрямую. `✘` означает, что watcher его не применяет; в зависимости от поля для полного эффекта требуется in-process reload runtime generation либо перезапуск процесса.
# Содержание # Содержание
- [Ключи верхнего уровня](#top-level-keys) - [Ключи верхнего уровня](#ключи-верхнего-уровня)
- [logging](#logging)
- [general](#general) - [general](#general)
- [general.modes](#generalmodes) - [general.modes](#generalmodes)
- [general.links](#generallinks) - [general.links](#generallinks)
@@ -42,6 +43,7 @@
| --- | ---- | ------- | ---------- | | --- | ---- | ------- | ---------- |
| [`include`](#include) | `String` (специальная директива) | — | `✔` | | [`include`](#include) | `String` (специальная директива) | — | `✔` |
| [`show_link`](#show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) | `✘` | | [`show_link`](#show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) | `✘` |
| [`logging`](#logging) | Таблица | значения по умолчанию | `✘` |
| [`dc_overrides`](#dc_overrides) | `Map<String, String or String[]>` | `{}` | `✘` | | [`dc_overrides`](#dc_overrides) | `Map<String, String or String[]>` | `{}` | `✘` |
| [`default_dc`](#default_dc) | `u8` | — (эффективный резервный вариант: `2` в ME маршрутизации) | `✘` | | [`default_dc`](#default_dc) | `u8` | — (эффективный резервный вариант: `2` в ME маршрутизации) | `✘` |
| [`beobachten`](#beobachten) | `bool` | `true` | `✘` | | [`beobachten`](#beobachten) | `bool` | `true` | `✘` |
@@ -80,7 +82,7 @@
"203" = ["149.154.175.100:443", "91.105.192.100:443"] "203" = ["149.154.175.100:443", "91.105.192.100:443"]
``` ```
## default_dc ## default_dc
- **Ограничения / валидация**: целочисленное значение в диапазоне `1..=5`. Если значение выходит за пределы диапазона, клиент направляется к DC1; Middle-end маршрутизация направляет клиента к DC2, если DC1 не задан. - **Ограничения / валидация**: Предполагаемый диапазон — `1..=5`. Явно заданное значение вне диапазона в Direct relay приводит к поведению DC1; при отсутствии значения Middle-End routing использует DC2.
- **Описание**: DC по умолчанию, используемый для нестандартных DC. Когда клиент запрашивает неизвестный/нестандартный DC без переопределения, telemt направляет его в этот кластер по умолчанию. - **Описание**: DC по умолчанию, используемый для нестандартных DC. Когда клиент запрашивает неизвестный/нестандартный DC без переопределения, telemt направляет его в этот кластер по умолчанию.
- **Пример**: - **Пример**:
@@ -90,6 +92,84 @@
default_dc = 2 default_dc = 2
``` ```
# [logging]
| Ключ | Тип | По умолчанию | Hot-Reload |
| --- | --- | --- | --- |
| [`destination`](#loggingdestination) | `"stderr"` / `"syslog"` / `"file"` | `"stderr"` | `✘` |
| [`path`](#loggingpath) | `String` | — | `✘` |
| [`rotation`](#loggingrotation) | `"never"` / `"minutely"` / `"hourly"` / `"daily"` / `"weekly"` | `"never"` | `✘` |
| [`max_size_bytes`](#loggingmax_size_bytes) | `u64` | `0` | `✘` |
| [`max_files`](#loggingmax_files) | `usize` | `0` | `✘` |
| [`max_age_secs`](#loggingmax_age_secs) | `u64` | `0` | `✘` |
## logging.destination
- **Ограничения / валидация**: Допустимы `stderr`, `syslog` или `file`. `syslog` поддерживается только на Unix. Для `file` требуется `logging.path`.
- **Описание**: Выбирает runtime log destination. CLI-флаги имеют приоритет.
- **Пример**:
```toml
[logging]
destination = "file"
path = "/var/log/telemt.log"
```
## logging.path
- **Ограничения / валидация**: Обязателен при `logging.destination = "file"`; не может быть пустым.
- **Описание**: Путь для файлового логирования. При time-based rotation имя файла используется как rolling prefix.
- **Пример**:
```toml
[logging]
destination = "file"
path = "/var/log/telemt.log"
```
## logging.rotation
- **Ограничения / валидация**: Допустимы `never`, `minutely`, `hourly`, `daily` или `weekly`.
- **Описание**: Интервал time-based file rotation. `weekly` выполняет ротацию на границе воскресенья по UTC. `never` пишет точно в `logging.path`, если size rotation не включена.
- **Пример**:
```toml
[logging]
destination = "file"
path = "/var/log/telemt.log"
rotation = "daily"
```
## logging.max_size_bytes
- **Ограничения / валидация**: `0` отключает size rotation.
- **Описание**: Ротирует непустой активный файл перед записью следующей целой записи, если она превысит этот предел в байтах.
- **Пример**:
```toml
[logging]
destination = "file"
path = "/var/log/telemt.log"
max_size_bytes = 104857600
```
## logging.max_files
- **Ограничения / валидация**: `0` отключает retention по количеству файлов.
- **Описание**: Сохраняет не более указанного числа совпадающих log files, включая активный файл и архивы. Активный файл retention cleanup не удаляет.
- **Пример**:
```toml
[logging]
destination = "file"
path = "/var/log/telemt.log"
rotation = "daily"
max_files = 14
```
## logging.max_age_secs
- **Ограничения / валидация**: `0` отключает retention по возрасту.
- **Описание**: Удаляет ротированные log files старше указанного числа секунд по времени изменения. Активный файл retention cleanup не удаляет.
- **Пример**:
```toml
[logging]
destination = "file"
path = "/var/log/telemt.log"
rotation = "daily"
max_age_secs = 1209600
```
# [general] # [general]
@@ -124,6 +204,7 @@
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` | | [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` |
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` | | [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` |
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` | | [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` |
| [`me_writer_byte_budget_bytes`](#me_writer_byte_budget_bytes) | `usize` | `33570816` | `✘` |
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` | | [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` |
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` | | [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` |
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` | | [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` |
@@ -136,6 +217,7 @@
| [`me_d2c_frame_buf_shrink_threshold_bytes`](#me_d2c_frame_buf_shrink_threshold_bytes) | `usize` | `262144` | `✔` | | [`me_d2c_frame_buf_shrink_threshold_bytes`](#me_d2c_frame_buf_shrink_threshold_bytes) | `usize` | `262144` | `✔` |
| [`direct_relay_copy_buf_c2s_bytes`](#direct_relay_copy_buf_c2s_bytes) | `usize` | `65536` | `✔` | | [`direct_relay_copy_buf_c2s_bytes`](#direct_relay_copy_buf_c2s_bytes) | `usize` | `65536` | `✔` |
| [`direct_relay_copy_buf_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` | | [`direct_relay_copy_buf_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` |
| [`direct_relay_buffer_budget_max_bytes`](#direct_relay_buffer_budget_max_bytes) | `usize` | `0` | `✘` |
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` | | [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` |
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` | | [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` |
| [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` | | [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` |
@@ -221,13 +303,14 @@
| [`me_pool_drain_soft_evict_per_writer`](#me_pool_drain_soft_evict_per_writer) | `u8` | `2` | `✘` | | [`me_pool_drain_soft_evict_per_writer`](#me_pool_drain_soft_evict_per_writer) | `u8` | `2` | `✘` |
| [`me_pool_drain_soft_evict_budget_per_core`](#me_pool_drain_soft_evict_budget_per_core) | `u16` | `16` | `✘` | | [`me_pool_drain_soft_evict_budget_per_core`](#me_pool_drain_soft_evict_budget_per_core) | `u16` | `16` | `✘` |
| [`me_pool_drain_soft_evict_cooldown_ms`](#me_pool_drain_soft_evict_cooldown_ms) | `u64` | `1000` | `✘` | | [`me_pool_drain_soft_evict_cooldown_ms`](#me_pool_drain_soft_evict_cooldown_ms) | `u64` | `1000` | `✘` |
| [`me_bind_stale_mode`](#me_bind_stale_mode) | `"never"`, `"ttl"`, or `"always"` | `"ttl"` | `✔` | | [`me_bind_stale_mode`](#me_bind_stale_mode) | `"never"`, `"ttl"`, or `"always"` | `"never"` | `✔` |
| [`me_bind_stale_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` | | [`me_bind_stale_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` |
| [`me_pool_min_fresh_ratio`](#me_pool_min_fresh_ratio) | `f32` | `0.8` | `✔` | | [`me_pool_min_fresh_ratio`](#me_pool_min_fresh_ratio) | `f32` | `0.8` | `✔` |
| [`me_reinit_drain_timeout_secs`](#me_reinit_drain_timeout_secs) | `u64` | `90` | `✔` | | [`me_reinit_drain_timeout_secs`](#me_reinit_drain_timeout_secs) | `u64` | `90` | `✔` |
| [`proxy_secret_auto_reload_secs`](#proxy_secret_auto_reload_secs) | `u64` | `3600` | `✔` | | [`proxy_secret_auto_reload_secs`](#proxy_secret_auto_reload_secs) | `u64` | `3600` | `✔` |
| [`proxy_config_auto_reload_secs`](#proxy_config_auto_reload_secs) | `u64` | `3600` | `✔` | | [`proxy_config_auto_reload_secs`](#proxy_config_auto_reload_secs) | `u64` | `3600` | `✔` |
| [`me_reinit_singleflight`](#me_reinit_singleflight) | `bool` | `true` | `✔` | | [`me_reinit_singleflight`](#me_reinit_singleflight) | `bool` | `true` | `✔` |
| [`me_reinit_max_concurrency`](#me_reinit_max_concurrency) | `usize` | `2` | `✔` |
| [`me_reinit_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` | `✘` | | [`me_reinit_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` | `✘` |
| [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` | `✔` | | [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` | `✔` |
| [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` | `✔` | | [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` | `✔` |
@@ -266,6 +349,8 @@
[general] [general]
config_strict = true config_strict = true
``` ```
- **Известное ограничение**: В этой ревизии `config_strict = true` отклоняет иначе поддерживаемые ключи `access.user_source_deny` и `[[upstreams]].prefer`. Оставляйте strict mode выключенным, если используется любой из них.
## prefer_ipv6 ## prefer_ipv6
- **Ограничения / валидация**: Устарело. Используйте `network.prefer`. - **Ограничения / валидация**: Устарело. Используйте `network.prefer`.
- **Описание**: Устаревший флаг предпочтения IPv6 перенесен в `network.prefer`. - **Описание**: Устаревший флаг предпочтения IPv6 перенесен в `network.prefer`.
@@ -402,8 +487,8 @@
stun_nat_probe_concurrency = 8 stun_nat_probe_concurrency = 8
``` ```
## middle_proxy_pool_size ## middle_proxy_pool_size
- **Ограничения / валидация**: `usize`. - **Ограничения / валидация**: `usize`. Перед передачей в ME initialization значение нормализуется как `max(value, 1)`.
- **Описание**: Размер пула записи ME. - **Описание**: Не влияющий на enforcement compatibility input, который сейчас выводится в ME initialization log. Active writer targets определяет DC-family floor policy, а не это значение.
- **Пример**: - **Пример**:
```toml ```toml
@@ -494,7 +579,7 @@
rpc_proxy_req_every = 0 rpc_proxy_req_every = 0
``` ```
## me_writer_cmd_channel_capacity ## me_writer_cmd_channel_capacity
- **Ограничения / валидация**: Должно быть `> 0`. - **Ограничения / валидация**: Должно быть в пределах `1..=16384`.
- **Описание**: Ёмкость (размер) канала команд для каждого отправителя. - **Описание**: Ёмкость (размер) канала команд для каждого отправителя.
- **Пример**: - **Пример**:
@@ -502,8 +587,17 @@
[general] [general]
me_writer_cmd_channel_capacity = 4096 me_writer_cmd_channel_capacity = 4096
``` ```
## me_writer_byte_budget_bytes
- **Ограничения / валидация**: Должно быть кратно `16384` и находиться между динамическим минимумом и `268435456`. Минимум равен `2 * general.max_client_frame + 256` с округлением вверх до `16384`; при стандартном размере frame он равен `33570816`.
- **Описание**: Бюджет резидентной памяти для очереди данных каждого ME writer. File watcher не пересоздаёт существующие writer для этого поля; значение применяется при построении нового поколения ME/runtime через API либо после перезапуска.
- **Пример**:
```toml
[general]
me_writer_byte_budget_bytes = 33570816
```
## me_route_channel_capacity ## me_route_channel_capacity
- **Ограничения / валидация**: Должно быть `> 0`. - **Ограничения / валидация**: Должно быть в пределах `1..=8192`.
- **Описание**: Количество ответов от ME, которое может одновременно находиться “в пути” или в очереди для одного соединения. - **Описание**: Количество ответов от ME, которое может одновременно находиться “в пути” или в очереди для одного соединения.
- **Пример**: - **Пример**:
@@ -512,7 +606,7 @@
me_route_channel_capacity = 768 me_route_channel_capacity = 768
``` ```
## me_c2me_channel_capacity ## me_c2me_channel_capacity
- **Ограничения / валидация**: Должно быть `> 0`. - **Ограничения / валидация**: Должно быть в пределах `1..=8192`.
- **Описание**: Емкость очереди команд для каждого клиента (client reader -> ME sender). - **Описание**: Емкость очереди команд для каждого клиента (client reader -> ME sender).
- **Пример**: - **Пример**:
@@ -610,6 +704,15 @@
[general] [general]
direct_relay_copy_buf_s2c_bytes = 262144 direct_relay_copy_buf_s2c_bytes = 262144
``` ```
## direct_relay_buffer_budget_max_bytes
- **Ограничения / валидация**: `0` либо значение, кратное `4096`, в диапазоне `16777216..=2147483648`.
- **Описание**: Process-wide жёсткий предел памяти Direct relay copy buffers; `0` вычисляет его при запуске по ограничениям памяти cgroup/хоста. Изменение откладывается до перезапуска процесса.
- **Пример**:
```toml
[general]
direct_relay_buffer_budget_max_bytes = 0
```
## crypto_pending_buffer ## crypto_pending_buffer
- **Ограничения / валидация**: `usize` (байт). - **Ограничения / валидация**: `usize` (байт).
- **Описание**:Максимальный объём ожидающих (неотправленных) зашифрованных данных в буфере client writer (в байтах). - **Описание**:Максимальный объём ожидающих (неотправленных) зашифрованных данных в буфере client writer (в байтах).
@@ -620,7 +723,7 @@
crypto_pending_buffer = 262144 crypto_pending_buffer = 262144
``` ```
## max_client_frame ## max_client_frame
- **Ограничения / валидация**: `usize` (байт). - **Ограничения / валидация**: Должно быть в пределах `4096..=16777216` (байт).
- **Описание**: Максимально допустимый размер кадра MTProto клиента (в байтах). - **Описание**: Максимально допустимый размер кадра MTProto клиента (в байтах).
- **Пример**: - **Пример**:
@@ -710,7 +813,7 @@
me_warmup_step_jitter_ms = 300 me_warmup_step_jitter_ms = 300
``` ```
## me_reconnect_max_concurrent_per_dc ## me_reconnect_max_concurrent_per_dc
- **Ограничения / валидация**: `u32`. - **Ограничения / валидация**: `u32`. Runtime использует эффективное значение `max(value, 1)`, поэтому `0` работает как `1`.
- **Описание**: Ограничить количество одновременно работающих процессов переподключения (reconnect workers) к DC во время восстановления работоспособности. - **Описание**: Ограничить количество одновременно работающих процессов переподключения (reconnect workers) к DC во время восстановления работоспособности.
- **Пример**: - **Пример**:
@@ -737,7 +840,7 @@
me_reconnect_backoff_cap_ms = 30000 me_reconnect_backoff_cap_ms = 30000
``` ```
## me_reconnect_fast_retry_count ## me_reconnect_fast_retry_count
- **Ограничения / валидация**: `u32`. - **Ограничения / валидация**: `u32`. Runtime использует эффективное значение `max(value, 1)`, поэтому `0` работает как `1`.
- **Описание**: Лимит немедленных повторных попыток подключения перед тем, как включается долгий backoff (увеличивающаяся задержка между попытками). - **Описание**: Лимит немедленных повторных попыток подключения перед тем, как включается долгий backoff (увеличивающаяся задержка между попытками).
- **Пример**: - **Пример**:
@@ -1133,7 +1236,7 @@
me_route_hybrid_max_wait_ms = 3000 me_route_hybrid_max_wait_ms = 3000
``` ```
## me_route_blocking_send_timeout_ms ## me_route_blocking_send_timeout_ms
- **Ограничения / валидация**: Должно быть в пределах `0..=5000` (миллисекунд). `0` - неограниченное время ожидания. - **Ограничения / валидация**: Должно быть в пределах `1..=5000` (миллисекунд).
- **Описание**: Максимальное время ожидания для блокировки отправки через канал маршрутизации при fallback. - **Описание**: Максимальное время ожидания для блокировки отправки через канал маршрутизации при fallback.
- **Пример**: - **Пример**:
@@ -1316,7 +1419,7 @@
``` ```
## me_pool_drain_ttl_secs ## me_pool_drain_ttl_secs
- **Ограничения / валидация**: `u64` (секунды). `0` - отключает период drain-TTL и подавляет предупреждения drain-TTL для ненулевых (непустых) writer’ов, находящихся в состоянии **draining**. - **Ограничения / валидация**: `u64` (секунды). `0` - отключает период drain-TTL и подавляет предупреждения drain-TTL для ненулевых (непустых) writer’ов, находящихся в состоянии **draining**.
- **Описание**: Временной интервал Drain-TTL для устаревших ME writer’ов после изменения карты endpoint’ов. В течение TTL устаревшие writer’ы могут использоваться только как fallback для новых биндов (в зависимости от политики биндов). - **Описание**: Возрастной порог предупреждений о долгом drain после изменения endpoint map и нижняя граница нормализации force-close timeout. Разрешение stale binds отдельно задают `me_bind_stale_mode` и `me_bind_stale_ttl_secs`.
- **Пример**: - **Пример**:
```toml ```toml
@@ -1396,7 +1499,7 @@
``` ```
## me_bind_stale_mode ## me_bind_stale_mode
- **Ограничения / валидация**: `"never"`, `"ttl"` или `"always"`. - **Ограничения / валидация**: `"never"`, `"ttl"` или `"always"`.
- **Описание**: Политика разрешения новых биндов к устаревшим writer’ам. - **Описание**: Политика новых binds на stale draining writers в непокрытых DC-family groups. Значение по умолчанию `never` требует полного покрытия groups перед частичным hardswap commit; `ttl` и `always` разрешают ограниченный policy fallback.
- **Пример**: - **Пример**:
```toml ```toml
@@ -1406,7 +1509,7 @@
``` ```
## me_bind_stale_ttl_secs ## me_bind_stale_ttl_secs
- **Ограничения / валидация**: `u64`. - **Ограничения / валидация**: `u64`.
- **Описание**: TTL для разрешения биндов к устаревшим writer’ам при режиме `ttl`. - **Описание**: TTL для разрешения binds к stale writers в режиме `ttl`; `0` отключает TTL expiry для разрешённых draining writers.
- **Пример**: - **Пример**:
```toml ```toml
@@ -1416,7 +1519,7 @@
``` ```
## me_pool_min_fresh_ratio ## me_pool_min_fresh_ratio
- **Ограничения / валидация**: Должно быть в пределах `[0.0, 1.0]`. - **Ограничения / валидация**: Должно быть в пределах `[0.0, 1.0]`.
- **Описание**: Минимальный коэффициент актуального (fresh) покрытия DC перед началом удаления устаревших writer’ов. - **Описание**: Минимальная доля fresh DC-family coverage при generation commit. При `me_bind_stale_mode = "never"` отсутствующие groups блокируют commit, даже если эта доля достигнута.
- **Пример**: - **Пример**:
```toml ```toml
@@ -1425,8 +1528,8 @@
me_pool_min_fresh_ratio = 0.9 me_pool_min_fresh_ratio = 0.9
``` ```
## me_reinit_drain_timeout_secs ## me_reinit_drain_timeout_secs
- **Ограничения / валидация**: `u64`. `0` - используется безопасный системный fallback. Если значение `> 0` и `< me_pool_drain_ttl_secs`, повышает его до значения TTL. - **Ограничения / валидация**: `u64`. `0` сначала выбирает runtime safety fallback 300 секунд; затем effective timeout повышается как минимум до `me_pool_drain_ttl_secs`.
- **Описание**: Таймаут принудительного закрытия устаревших writer’ов при очистке/повторной инициализации. При `0` используется безопасный системный fallback (300 секунд). - **Описание**: Таймаут принудительного закрытия draining stale writers. Effective value равен максимуму из заданного ненулевого значения (или 300 секунд при `0`) и drain TTL.
- **Пример**: - **Пример**:
```toml ```toml
@@ -1467,9 +1570,18 @@
[general] [general]
me_reinit_singleflight = true me_reinit_singleflight = true
``` ```
## me_reinit_max_concurrency
- **Ограничения / валидация**: Должно быть в пределах `[1, 8]`. Эффективное значение равно `1`, пока `me_reinit_singleflight = true`.
- **Описание**: Ограничивает число одновременных прогревов поколений ME; лишние триггеры объединяются в один ожидающий повторный запуск.
- **Пример**:
```toml
[general]
me_reinit_max_concurrency = 2
```
## me_reinit_trigger_channel ## me_reinit_trigger_channel
- **Ограничения / валидация**: Должно быть `> 0`. - **Ограничения / валидация**: Должно быть в пределах `[1, 4096]`.
- **Описание**: Емкость очереди триггеров для планировщика повторной инициализации. - **Описание**: Ёмкость очереди triggers для reinit scheduler. Новая runtime generation создаёт канал из этого значения; один file watcher не изменяет размер активного канала.
- **Пример**: - **Пример**:
```toml ```toml
@@ -2487,6 +2599,8 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
| `carriers` | `false` или непустой массив уникальных carrier | `false` | `✔` | | `carriers` | `false` или непустой массив уникальных carrier | `false` | `✔` |
| `carrier_learning` | `bool` | `true` | `✔` | | `carrier_learning` | `bool` | `true` | `✔` |
| `carrier_negotiation_aggressiveness` | `"conservative"`, `"balanced"` или `"aggressive"` | `"conservative"` | `✔` | | `carrier_negotiation_aggressiveness` | `"conservative"`, `"balanced"` или `"aggressive"` | `"conservative"` | `✔` |
| `decoy_fasttrack_mode` | `"off"`, `"shadow"` или `"enforce"` | `"off"` | `✘` |
| `http_connection_capacity_action` | `"drop"`, `"wait"` или `"respond"` | `"drop"` | `✔` |
| `debug` | таблица | выключено, ограниченные defaults | `✔` | | `debug` | таблица | выключено, ограниченные defaults | `✔` |
| `limits` | таблица | ограниченные defaults | `✘` | | `limits` | таблица | ограниченные defaults | `✘` |
| `timeouts` | таблица | ограниченные defaults | `✔` | | `timeouts` | таблица | ограниченные defaults | `✔` |
@@ -2496,6 +2610,10 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
Если `carriers` отсутствует или равен `false`, auto-negotiation и обучение выключены, а `carrier` задаёт единственный режим. Непустой массив `carriers` включает стартовый перебор в заданном порядке; `carrier` ровно один раз добавляется последним fallback-вариантом. Пустой массив, дубликаты и `true` запрещены. Клиент может перейти к следующему кандидату только до commit carrier; для смены carrier после commit нужна новая сессия. Native-клиент без метаданных, включая Telegram iOS, всегда использует настроенный фиксированный `carrier`, даже при включённом auto-negotiation. Текущий iOS поддерживает только `https`, поэтому такой deployment должен задавать `carrier = "https"`. Классификация User-Agent CFNetwork и Darwin не определяет поддержку carrier. Явные capabilities нативного iOS пересекаются с `{https}`; capabilities остальных явных клиентов применяются как переданы. Если `carriers` отсутствует или равен `false`, auto-negotiation и обучение выключены, а `carrier` задаёт единственный режим. Непустой массив `carriers` включает стартовый перебор в заданном порядке; `carrier` ровно один раз добавляется последним fallback-вариантом. Пустой массив, дубликаты и `true` запрещены. Клиент может перейти к следующему кандидату только до commit carrier; для смены carrier после commit нужна новая сессия. Native-клиент без метаданных, включая Telegram iOS, всегда использует настроенный фиксированный `carrier`, даже при включённом auto-negotiation. Текущий iOS поддерживает только `https`, поэтому такой deployment должен задавать `carrier = "https"`. Классификация User-Agent CFNetwork и Darwin не определяет поддержку carrier. Явные capabilities нативного iOS пересекаются с `{https}`; capabilities остальных явных клиентов применяются как переданы.
`http_connection_capacity_action` применяется только после того, как Telemt принял приватное WEB TCP connection и исчерпал `max_http_connections`. `drop` сохраняет немедленное закрытие. `respond` отправляет пустой повторяемый `503 Service Unavailable` с `Retry-After: 1`, `Cache-Control: no-store` и `Connection: close`. `wait` ожидает обычную capacity не более `http_overload_timeout_ms`, затем переходит к нормальной HTTP-обработке; по timeout отправляется тот же ограниченный `503`. Вне обычной capacity могут ожидать или отвечать не более `max_http_overload_connections` принятых sockets.
`decoy_fasttrack_mode` требует перезапуска и управляет только capability work для `GET/HEAD` на настроенном base root. `off` сохраняет полный scan, `shadow` считает подходящие requests, но не пропускает scan, а `enforce` пропускает его только для `HEAD` или отсутствующего/неканонического параметра `bridge`. Канонический bridge-shaped `GET` всегда сканирует все профили выбранного vhost. Оптимизация не ограничивает враждебные канонические probes; `enforce` необходимо проверять на различимость timing за production TLS-терминатором.
`carrier_learning` действует только при включённом auto-negotiation. Обучение локально для процесса, хранится в памяти, ограничено и учитывает только положительный результат: evidence добавляет лишь carrier, достигший определённого сервером состояния healthy. `conservative` требует наиболее широкой выборки и отключает ранжирование по IP, `balanced` использует умеренные пороги для User-Agent/профиля и допустимый публичный IP только для разрешения равенства, а `aggressive` реагирует на первые ограниченные samples. Сообщённые клиентом ошибки остаются только диагностикой и не создают отрицательный evidence. Reload применяет новую policy к новым цепочкам negotiation и инвалидирует несовместимый сохранённый evidence. Отключение WEB прекращает выдачу новых bridge- и session-credentials; для отзыва активных сессий отдельного пользователя используйте users API. `carrier_learning` действует только при включённом auto-negotiation. Обучение локально для процесса, хранится в памяти, ограничено и учитывает только положительный результат: evidence добавляет лишь carrier, достигший определённого сервером состояния healthy. `conservative` требует наиболее широкой выборки и отключает ранжирование по IP, `balanced` использует умеренные пороги для User-Agent/профиля и допустимый публичный IP только для разрешения равенства, а `aggressive` реагирует на первые ограниченные samples. Сообщённые клиентом ошибки остаются только диагностикой и не создают отрицательный evidence. Reload применяет новую policy к новым цепочкам negotiation и инвалидирует несовместимый сохранённый evidence. Отключение WEB прекращает выдачу новых bridge- и session-credentials; для отзыва активных сессий отдельного пользователя используйте users API.
# [web.debug] # [web.debug]
@@ -2506,6 +2624,7 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `enabled` | `bool` | `false` | Включает WEB HTTP, WebSocket-message, frame и lifecycle debug records. | | `enabled` | `bool` | `false` | Включает WEB HTTP, WebSocket-message, frame и lifecycle debug records. |
| `capture_lifecycle` | `bool` | `true` | Записывает типизированные события bridge, session, stream, handshake, relay и close. | | `capture_lifecycle` | `bool` | `true` | Записывает типизированные события bridge, session, stream, handshake, relay и close. |
| `sideband` | `bool` | `false` | Включает lifecycle diagnostics из сгенерированного bridge; действует только вместе с `enabled` и `capture_lifecycle`. |
| `capture_headers` | `bool` | `true` | Сохраняет имена headers и только разрешённые значения без credentials. | | `capture_headers` | `bool` | `true` | Сохраняет имена headers и только разрешённые значения без credentials. |
| `capture_timings` | `bool` | `true` | Сохраняет timing points для request body, готового response, response body и обработки WebSocket messages. | | `capture_timings` | `bool` | `true` | Сохраняет timing points для request body, готового response, response body и обработки WebSocket messages. |
| `capture_frames` | `bool` | `true` | Разбирает bounded carrier bodies в тип frame, stream ID, длину, WINDOW и метаданные ошибок, не сохраняя frame payload отдельно. | | `capture_frames` | `bool` | `true` | Разбирает bounded carrier bodies в тип frame, stream ID, длину, WINDOW и метаданные ошибок, не сохраняя frame payload отдельно. |
@@ -2517,6 +2636,8 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
Изменение `enabled` или любого поля capture очищает сохранённые записи и отклоняет commits, начатые в предыдущую policy epoch. Изменение только стандартного или максимального окна наблюдения сохраняет совместимые записи. `full` сохраняет полное тело распознанного carrier только до `web.limits.max_body_bytes`; decoy bodies всегда остаются ограничены настроенным prefix. Prefix, который помещается только в одновременно увеличенную restart-only ёмкость, откладывается вместе с `web.debug` до перезапуска. URI queries никогда не сохраняются, значения credential headers исключаются, копии body очищаются от известных WEB capabilities и bearer tokens, а ключи профилей представлены только domain-separated fingerprint из 16 hex-символов. Изменение `enabled` или любого поля capture очищает сохранённые записи и отклоняет commits, начатые в предыдущую policy epoch. Изменение только стандартного или максимального окна наблюдения сохраняет совместимые записи. `full` сохраняет полное тело распознанного carrier только до `web.limits.max_body_bytes`; decoy bodies всегда остаются ограничены настроенным prefix. Prefix, который помещается только в одновременно увеличенную restart-only ёмкость, откладывается вместе с `web.debug` до перезапуска. URI queries никогда не сохраняются, значения credential headers исключаются, копии body очищаются от известных WEB capabilities и bearer tokens, а ключи профилей представлены только domain-separated fingerprint из 16 hex-символов.
Когда `enabled`, `sideband` и `capture_lifecycle` одновременно включены, новые сгенерированные bridge pages отправляют ограниченные одноразовые lifecycle events по точному настроенному base плюс `api/v1/diagnostic`. Это внутренний route Telemt, а не публичный Control API. Уже выданные bridge documents не получают sideband после reload.
Аутентифицированное JSON-управление может явно очистить ring через `POST /v1/runtime/web/debug/clear`: обязательный process `runtime_instance` защищает от устаревшего controller, возвращаемый epoch отсекает in-flight writers, а `leased_bytes` показывает память, всё ещё удерживаемую уже отрисовываемыми snapshots. Аутентифицированное JSON-управление может явно очистить ring через `POST /v1/runtime/web/debug/clear`: обязательный process `runtime_instance` защищает от устаревшего controller, возвращаемый epoch отсекает in-flight writers, а `leased_bytes` показывает память, всё ещё удерживаемую уже отрисовываемыми snapshots.
# [web.limits] # [web.limits]
@@ -2531,6 +2652,7 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
| `carrier_batch_bytes` | `usize` | `2097152` | Максимальный закодированный downlink batch. | | `carrier_batch_bytes` | `usize` | `2097152` | Максимальный закодированный downlink batch. |
| `max_frames_per_body` | `usize` | `4096` | Максимальное число frames в одном carrier body. | | `max_frames_per_body` | `usize` | `4096` | Максимальное число frames в одном carrier body. |
| `max_http_connections` | `usize` | `1024` | Принятые WEB HTTP connections на весь процесс. | | `max_http_connections` | `usize` | `1024` | Принятые WEB HTTP connections на весь процесс. |
| `max_http_overload_connections` | `usize` | `64` | Принятые перегруженные sockets, которым разрешено ожидать или отправить ограниченный повторяемый ответ вне обычной HTTP capacity. |
| `max_http_handlers` | `usize` | `512` | Одновременно выполняемые HTTP handlers на весь процесс; HTTPS lanes могут занять long polls не более половины лимита, оставляя остаток для session, uplink и control work. | | `max_http_handlers` | `usize` | `512` | Одновременно выполняемые HTTP handlers на весь процесс; HTTPS lanes могут занять long polls не более половины лимита, оставляя остаток для session, uplink и control work. |
| `max_lane_open_waits_per_session` | `usize` | `16` | Канонические downlink polls с cursor zero, которые могут ожидать конкурирующий lane `OPEN` в одной сессии. | | `max_lane_open_waits_per_session` | `usize` | `16` | Канонические downlink polls с cursor zero, которые могут ожидать конкурирующий lane `OPEN` в одной сессии. |
| `pending_bytes_per_lane` | `usize` | `8388608` | Байты queued и resident `DATA`, разрешённые одной независимой HTTPS- или WebSocket-lane. | | `pending_bytes_per_lane` | `usize` | `8388608` | Байты queued и resident `DATA`, разрешённые одной независимой HTTPS- или WebSocket-lane. |
@@ -2585,6 +2707,7 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
| `long_poll_secs` | `u64` | `25` | `✔` | Максимальная длительность пустого downlink long poll. | | `long_poll_secs` | `u64` | `25` | `✔` | Максимальная длительность пустого downlink long poll. |
| `bridge_request_secs` | `u64` | `10` | `✔` | Deadline одной HTTP attempt в bridge до полного чтения response body; для `/down` дополнительно разрешён `long_poll_secs`. Диапазон `1..=60`. | | `bridge_request_secs` | `u64` | `10` | `✔` | Deadline одной HTTP attempt в bridge до полного чтения response body; для `/down` дополнительно разрешён `long_poll_secs`. Диапазон `1..=60`. |
| `bridge_retry_secs` | `u64` | `90` | `✔` | Абсолютное окно повторов bridge, включая attempts и backoff; диапазон `1..=300`, не меньше `bridge_request_secs`. | | `bridge_retry_secs` | `u64` | `90` | `✔` | Абсолютное окно повторов bridge, включая attempts и backoff; диапазон `1..=300`, не меньше `bridge_request_secs`. |
| `bridge_recovery_secs` | `u64` | `15` | `✔` | Абсолютное окно recovery после commit для сохранившегося bridge document; диапазон `1..=60`, фиксируется при начале recovery. |
| `carrier_probe_coalesce_ms` | `u64` | `0` | `✔` | Опциональное ожидание bridge после `OPEN` для соответствующего `DATA`; миллисекунды в диапазоне `0..=10`, где `0` сохраняет немедленный probe. | | `carrier_probe_coalesce_ms` | `u64` | `0` | `✔` | Опциональное ожидание bridge после `OPEN` для соответствующего `DATA`; миллисекунды в диапазоне `0..=10`, где `0` сохраняет немедленный probe. |
| `lane_open_wait_secs` | `u64` | `2` | `✔` | Ожидание канонического downlink с cursor zero, опередившего свой lane `OPEN`; не больше `long_poll_secs`. | | `lane_open_wait_secs` | `u64` | `2` | `✔` | Ожидание канонического downlink с cursor zero, опередившего свой lane `OPEN`; не больше `long_poll_secs`. |
| `carrier_health_secs` | `u64` | `30` | `✔` | Интервал наблюдения после commit, необходимый для добавления carrier-learning evidence. | | `carrier_health_secs` | `u64` | `30` | `✔` | Интервал наблюдения после commit, необходимый для добавления carrier-learning evidence. |
@@ -2598,6 +2721,7 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
| `bootstrap_lifetime_secs` | `u64` | `120` | `✔` | Срок неиспользованного bootstrap и replay-marker закрытого token. | | `bootstrap_lifetime_secs` | `u64` | `120` | `✔` | Срок неиспользованного bootstrap и replay-marker закрытого token. |
| `reconnect_grace_secs` | `u64` | `120` | `✔` | Максимальная неактивность carrier до закрытия сессии. | | `reconnect_grace_secs` | `u64` | `120` | `✔` | Максимальная неактивность carrier до закрытия сессии. |
| `http_idle_secs` | `u64` | `75` | `✔` | Лимит простоя между HTTP-обменами и при отсутствии прогресса уже выданного response body. Явно ограниченные фазы request body, long poll, decoy и ожидания Upgrade сохраняют собственные deadlines и не обрываются этим таймером. Значение фиксируется при приёме connection. | | `http_idle_secs` | `u64` | `75` | `✔` | Лимит простоя между HTTP-обменами и при отсутствии прогресса уже выданного response body. Явно ограниченные фазы request body, long poll, decoy и ожидания Upgrade сохраняют собственные deadlines и не обрываются этим таймером. Значение фиксируется при приёме connection. |
| `http_overload_timeout_ms` | `u64` | `250` | `✔` | Deadline каждой фазы в миллисекундах для ожидания capacity или записи повторяемого ответа после принятия перегруженного socket; диапазон `1..=60000`. Timeout ожидания и запись ответа получают не более одного бюджета фазы каждый. |
| `shutdown_secs` | `u64` | `15` | `✔` | Один абсолютный бюджет завершения процесса, общий для всех listener acceptors и connections, а также для WEB sessions и auxiliary tasks. Активное значение фиксируется один раз при начале shutdown. | | `shutdown_secs` | `u64` | `15` | `✔` | Один абсолютный бюджет завершения процесса, общий для всех listener acceptors и connections, а также для WEB sessions и auxiliary tasks. Активное значение фиксируется один раз при начале shutdown. |
| `decoy_header_secs` | `u64` | `30` | `✔` | Deadline подключения и получения response head от HTTP decoy. | | `decoy_header_secs` | `u64` | `30` | `✔` | Deadline подключения и получения response head от HTTP decoy. |
@@ -2606,11 +2730,12 @@ WEB-режим переносит MTProxy-трафик Telegram Desktop внут
| Ключ | Тип | Обязательный | Hot-Reload | Описание | | Ключ | Тип | Обязательный | Hot-Reload | Описание |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `host` | `String` | да | `✔` | Уникальный канонический lowercase ACE FQDN без порта, пути, credentials и завершающей точки. | | `host` | `String` | да | `✔` | Уникальный канонический lowercase ACE FQDN без порта, пути, credentials и завершающей точки. |
| `base_path` | `String` | нет | `✔` | Точный регистрозависимый WEB-prefix без начального и завершающего слеша; по умолчанию пуст. Не более 128 ASCII-байт в разделённых слешами сегментах `[A-Za-z0-9][A-Za-z0-9_-]*`. |
| `public_addr` | `SocketAddr` | да | `✔` | Конкретный публичный IP на порту `443`, используемый во внутреннем destination tuple relay. | | `public_addr` | `SocketAddr` | да | `✔` | Конкретный публичный IP на порту `443`, используемый во внутреннем destination tuple relay. |
| `decoy` | таблица | да | `✔` | Обычный сайт для неаутентифицированного или некорректного трафика. | | `decoy` | таблица | да | `✔` | Обычный сайт для неаутентифицированного или некорректного трафика. |
| `profiles` | массив таблиц | при включённом WEB | `✔` | Явные пользователи и client secret modes для этого hostname. | | `profiles` | массив таблиц | при включённом WEB | `✔` | Явные пользователи и client secret modes для этого hostname. |
Hostname нормализуется при валидации и должен приниматься Telegram Desktop. Bootstrap является bearer credential: адрес клиента и его IP-семейство могут измениться до создания session. Неиспользованный bootstrap остаётся действительным после reload конфигурации, только пока активен профиль с той же identity. Hostname нормализуется при валидации и должен приниматься Telegram Desktop. Пустой `base_path` сохраняет root-capability v1 и прежний шестнадцатеричный secret ссылки. Непустой путь использует capability v2 по host/path и Telegram Desktop path-ссылку с percent-encoded `HOST/BASE` и base64url-маркером secret `0x70`. Маршрутизация требует точного prefix с завершающим слешем и никогда не перенаправляет, не нормализует и не удаляет его. Bootstrap является bearer credential: адрес клиента и его IP-семейство могут измениться до создания session. Неиспользованный bootstrap остаётся действительным после reload конфигурации, только пока активен профиль с той же identity.
# [web.vhosts.decoy] # [web.vhosts.decoy]
@@ -2636,6 +2761,7 @@ Hostname нормализуется при валидации и должен п
## Lifecycle WEB и управление через API ## Lifecycle WEB и управление через API
- Config watcher и generation reload применяют `web.enabled`, policy carrier/negotiation, `web.debug`, `web.timeouts`, vhosts, profiles и decoy snapshots без перезапуска процесса. Валидируется и активируется один immutable expanded source snapshot; watcher candidate generation запускается только после активации этого поколения. Существующие сессии и начатые negotiation chains сохраняют issuance-time carrier candidates, limits, timeouts и абсолютные deadlines; новые bridge sessions используют одно зафиксированное активное поколение. - Config watcher и generation reload применяют `web.enabled`, policy carrier/negotiation, `web.debug`, `web.timeouts`, vhosts, profiles и decoy snapshots без перезапуска процесса. Валидируется и активируется один immutable expanded source snapshot; watcher candidate generation запускается только после активации этого поколения. Существующие сессии и начатые negotiation chains сохраняют issuance-time carrier candidates, limits, timeouts и абсолютные deadlines; новые bridge sessions используют одно зафиксированное активное поколение.
- Изменение `base_path` атомарно заменяет и маршрут новых запросов, и производную capability. Сначала выпустите новые ссылки и завершите затронутые активные sessions: установленные WebSockets и уже маршрутизированные обмены продолжаются; последующие запросы к старому base с подлинным для процесса bootstrap- или session-token получают локальный no-store `404`, а ставшая неактивной прежняя capability обрабатывается как обычный decoy traffic.
- Состав WEB-listeners и их trust policy в `server.listeners`, а также все значения `web.limits` принадлежат процессу и требуют перезапуска. - Состав WEB-listeners и их trust policy в `server.listeners`, а также все значения `web.limits` принадлежат процессу и требуют перезапуска.
- `GET /v1/config` возвращает полное авторское дерево `[web]`, кроме производного snapshot `web.runtime`. `PATCH /v1/config` принимает sparse object `web`, глубоко сливает tables, целиком заменяет arrays, валидирует полный candidate и указывает `web.limits` в `deferred_process_fields` до перезапуска. - `GET /v1/config` возвращает полное авторское дерево `[web]`, кроме производного snapshot `web.runtime`. `PATCH /v1/config` принимает sparse object `web`, глубоко сливает tables, целиком заменяет arrays, валидирует полный candidate и указывает `web.limits` в `deferred_process_fields` до перезапуска.
- `GET /v1/runtime/web/status`, `/sessions`, `/sessions/{session_ref}` и `/operations/{operation_id}` предоставляют bounded несекретное runtime-состояние. POST controls закрывают выбранные сессии, очищают debug или сбрасывают carrier learning и требуют текущий случайный `runtime_instance`. - `GET /v1/runtime/web/status`, `/sessions`, `/sessions/{session_ref}` и `/operations/{operation_id}` предоставляют bounded несекретное runtime-состояние. POST controls закрывают выбранные сессии, очищают debug или сбрасывают carrier learning и требуют текущий случайный `runtime_instance`.
@@ -2763,8 +2889,10 @@ Hostname нормализуется при валидации и должен п
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` | | [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` |
| [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults | `✘` | | [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults | `✘` |
| [`mask`](#mask) | `bool` | `true` | `✘` | | [`mask`](#mask) | `bool` | `true` | `✘` |
| [`mask_dynamic`](#mask_dynamic) | `bool` | `true` | `✘` |
| [`mask_host`](#mask_host) | `String` | — | `✘` | | [`mask_host`](#mask_host) | `String` | — | `✘` |
| [`mask_port`](#mask_port) | `u16` | `443` | `✘` | | [`mask_port`](#mask_port) | `u16` | `443` | `✘` |
| [`exclusive_mask`](#exclusive_mask) | `Map<String, String>` | `{}` | `✘` |
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` | | [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` |
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` | | [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` |
| [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` | | [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` |
@@ -2783,8 +2911,8 @@ Hostname нормализуется при валидации и должен п
| [`mask_shape_above_cap_blur`](#mask_shape_above_cap_blur) | `bool` | `false` | `✘` | | [`mask_shape_above_cap_blur`](#mask_shape_above_cap_blur) | `bool` | `false` | `✘` |
| [`mask_shape_above_cap_blur_max_bytes`](#mask_shape_above_cap_blur_max_bytes) | `usize` | `512` | `✘` | | [`mask_shape_above_cap_blur_max_bytes`](#mask_shape_above_cap_blur_max_bytes) | `usize` | `512` | `✘` |
| [`mask_relay_max_bytes`](#mask_relay_max_bytes) | `usize` | `5242880` | `✘` | | [`mask_relay_max_bytes`](#mask_relay_max_bytes) | `usize` | `5242880` | `✘` |
| [`mask_relay_timeout_ms`](mask_relay_timeout_ms) | `u64` | `60_000` | `✘` | | [`mask_relay_timeout_ms`](#mask_relay_timeout_ms) | `u64` | `60_000` | `✘` |
| [`mask_relay_idle_timeout_ms`](mask_relay_idle_timeout_ms) | `u64` | `5_000` | `✘` | | [`mask_relay_idle_timeout_ms`](#mask_relay_idle_timeout_ms) | `u64` | `5_000` | `✘` |
| [`mask_classifier_prefetch_timeout_ms`](#mask_classifier_prefetch_timeout_ms) | `u64` | `5` | `✘` | | [`mask_classifier_prefetch_timeout_ms`](#mask_classifier_prefetch_timeout_ms) | `u64` | `5` | `✘` |
| [`mask_timing_normalization_enabled`](#mask_timing_normalization_enabled) | `bool` | `false` | `✘` | | [`mask_timing_normalization_enabled`](#mask_timing_normalization_enabled) | `bool` | `false` | `✘` |
| [`mask_timing_normalization_floor_ms`](#mask_timing_normalization_floor_ms) | `u64` | `0` | `✘` | | [`mask_timing_normalization_floor_ms`](#mask_timing_normalization_floor_ms) | `u64` | `0` | `✘` |
@@ -2831,9 +2959,9 @@ Hostname нормализуется при валидации и должен п
[censorship] [censorship]
tls_fetch_scope = "fetch" tls_fetch_scope = "fetch"
``` ```
# censorship.tls_fetch ## tls_fetch
- **Ограничения / валидация**: Таблица, см. секцию `[censorship.tls_fetch]` ниже. - **Ограничения / валидация**: Таблица, см. секцию `[censorship.tls_fetch]` ниже.
- **Описание**: Настройки стратегии получения TLS-front метаданных (поведение загрузки и обновления bootstrap и данных эмуляции TLS).. - **Описание**: Настройки стратегии получения TLS-front метаданных (поведение загрузки и обновления bootstrap и данных эмуляции TLS).
- **Пример**: - **Пример**:
```toml ```toml
@@ -2844,18 +2972,27 @@ Hostname нормализуется при валидации и должен п
``` ```
## mask ## mask
- **Ограничения / валидация**: `bool`. - **Ограничения / валидация**: `bool`.
- **Описание**: Включает режим маскировки/верхнего уровня. Принимаются все SNI, которые похожи на заданный в `tls_domain`. - **Описание**: Включает режим masking/fronting relay.
- **Пример**: - **Пример**:
```toml ```toml
[censorship] [censorship]
mask = true mask = true
``` ```
## mask_dynamic
- **Ограничения / валидация**: `bool`.
- **Описание**: Когда не заданы ни `mask_host`, ни `mask_unix_sock`, совпадающий с `tls_domain`/`tls_domains` SNI из ClientHello используется как TCP mask target; при отсутствии совпадения применяется основной `tls_domain`. Совпавший `exclusive_mask` всегда имеет приоритет над обычными целями.
- **Пример**:
```toml
[censorship]
mask_dynamic = true
```
## mask_host ## mask_host
- **Ограничения / валидация**: `String` (необязательный параметр). - **Ограничения / валидация**: `String` (необязательный параметр).
- Если задан параметр `mask_unix_sock`, `mask_host` не должен быть задан. - Если задан параметр `mask_unix_sock`, `mask_host` не должен быть задан.
- Если не задан параметр `mask_host` и `mask_unix_sock` не задан, Telemt по умолчанию устанавливает для `mask_host` значение `tls_domain`. - Если не заданы ни `mask_host`, ни `mask_unix_sock`, `mask_dynamic` может выбрать совпадающий настроенный SNI; иначе Telemt использует `tls_domain`.
- **Описание**: Хост, используемый для маскировки при TLS-fronting. - **Описание**: Явный upstream host для TLS-fronting relay. Если он задан, dynamic SNI target selection отключён, кроме переопределений `exclusive_mask`.
- **Пример**: - **Пример**:
```toml ```toml
@@ -3303,6 +3440,7 @@ Hostname нормализуется при валидации и должен п
| Ключ | Тип | По умолчанию | Hot-Reload | | Ключ | Тип | По умолчанию | Hot-Reload |
| --- | ---- | ------- | ---------- | | --- | ---- | ------- | ---------- |
| [`users`](#users) | `Map<String, String>` | `{"default": "000…000"}` | `✔` | | [`users`](#users) | `Map<String, String>` | `{"default": "000…000"}` | `✔` |
| [`user_enabled`](#user_enabled-1) | `Map<String, bool>` | `{}` | `✔` |
| [`user_ad_tags`](#user_ad_tags) | `Map<String, String>` | `{}` | `✔` | | [`user_ad_tags`](#user_ad_tags) | `Map<String, String>` | `{}` | `✔` |
| [`user_max_tcp_conns`](#user_max_tcp_conns) | `Map<String, usize>` | `{}` | `✔` | | [`user_max_tcp_conns`](#user_max_tcp_conns) | `Map<String, usize>` | `{}` | `✔` |
| [`user_max_tcp_conns_global_each`](#user_max_tcp_conns_global_each) | `usize` | `0` | `✔` | | [`user_max_tcp_conns_global_each`](#user_max_tcp_conns_global_each) | `usize` | `0` | `✔` |
@@ -3329,6 +3467,16 @@ Hostname нормализуется при валидации и должен п
alice = "00112233445566778899aabbccddeeff" alice = "00112233445566778899aabbccddeeff"
bob = "0123456789abcdef0123456789abcdef" bob = "0123456789abcdef0123456789abcdef"
``` ```
## user_enabled
- **Ограничения / валидация**: `Map<String, bool>`.
- **Описание**: Необязательные per-user overrides. Пользователь без записи включён. `false` запрещает новые sessions; `true` допустим, но эквивалентен удалению override. API enable удаляет override, а disable записывает `false`.
- **Runtime-поведение**: Hot reload применяет карту немедленно. После успешной аутентификации отключённому пользователю отказывают, а его активные runtime sessions отменяются.
- **Пример**:
```toml
[access.user_enabled]
alice = false
```
## user_ad_tags ## user_ad_tags
- **Ограничения / валидация**: Каждое значение должно содержать **ровно 32 шестнадцатеричных символа** (тот же формат, что и в `general.ad_tag`). Тег со всеми нулями разрешен, но в логи будет записано предупреждение. - **Ограничения / валидация**: Каждое значение должно содержать **ровно 32 шестнадцатеричных символа** (тот же формат, что и в `general.ad_tag`). Тег со всеми нулями разрешен, но в логи будет записано предупреждение.
- **Описание**: Переопределение рекламного тега спонсируемого канала для каждого пользователя. Когда у пользователя есть запись здесь, она имеет приоритет над `general.ad_tag`. - **Описание**: Переопределение рекламного тега спонсируемого канала для каждого пользователя. Когда у пользователя есть запись здесь, она имеет приоритет над `general.ad_tag`.
@@ -3360,8 +3508,9 @@ Hostname нормализуется при валидации и должен п
user_max_tcp_conns_global_each = 200 user_max_tcp_conns_global_each = 200
[access.user_max_tcp_conns] [access.user_max_tcp_conns]
alice = 500 # uses 500, not the global cap # Alice uses 500 rather than the global cap.
# bob has no entry > uses 200 alice = 500
# Bob has no entry and therefore uses 200.
``` ```
## user_expirations ## user_expirations
- **Ограничения / валидация**: `Map<String, DateTime<Utc>>`. Каждое значение должно быть валидной датой и временем в формате RFC3339/ISO-8601. - **Ограничения / валидация**: `Map<String, DateTime<Utc>>`. Каждое значение должно быть валидной датой и временем в формате RFC3339/ISO-8601.
@@ -3379,7 +3528,8 @@ Hostname нормализуется при валидации и должен п
```toml ```toml
[access.user_data_quota] [access.user_data_quota]
alice = 1073741824 # 1 GiB # Alice receives a 1 GiB quota.
alice = 1073741824
``` ```
## user_max_unique_ips ## user_max_unique_ips
- **Ограничения / валидация**: `Map<String, usize>`. - **Ограничения / валидация**: `Map<String, usize>`.
@@ -3461,7 +3611,7 @@ Hostname нормализуется при валидации и должен п
## user_rate_limits ## user_rate_limits
- **Ограничения / валидация**: Таблица `username -> { up_bps, down_bps }`. Должно быть ненулевое значение хотя бы в одном направлении. - **Ограничения / валидация**: Таблица `username -> { up_bps, down_bps }`. Каждое направление должно быть в диапазоне `0..=100000000000`; `0` означает отсутствие лимита в этом направлении, при этом хотя бы одно направление должно быть ненулевым.
- **Описание**: Персональные лимиты скорости по пользователям в битах/сек для отправки (`up_bps`) и получения (`down_bps`). - **Описание**: Персональные лимиты скорости по пользователям в битах/сек для отправки (`up_bps`) и получения (`down_bps`).
- **Example**: - **Example**:
@@ -3470,7 +3620,7 @@ Hostname нормализуется при валидации и должен п
alice = { up_bps = 1048576, down_bps = 2097152 } alice = { up_bps = 1048576, down_bps = 2097152 }
``` ```
## cidr_rate_limits ## cidr_rate_limits
- **Ограничения / валидация**: Таблица `CIDR или auto-template -> { up_bps, down_bps }`. Explicit CIDR-ключи должны корректно разбираться как `IpNetwork`; auto-template ключи должны иметь вид `*4/N` (`N=0..32`), `*6/N` (`N=0..128`) или `*/N` (`N=0..32`). Хотя бы одно направление должно быть ненулевым. Дублирующиеся нормализованные auto-template отклоняются. - **Ограничения / валидация**: Таблица `CIDR или auto-template -> { up_bps, down_bps }`. Каждое направление должно быть в диапазоне `0..=100000000000`; `0` означает отсутствие лимита в этом направлении, при этом хотя бы одно направление должно быть ненулевым. Explicit CIDR-ключи должны корректно разбираться как `IpNetwork`; auto-template ключи должны иметь вид `*4/N` (`N=0..32`), `*6/N` (`N=0..128`) или `*/N` (`N=0..32`). Дублирующиеся нормализованные auto-template отклоняются.
- **Описание**: Лимиты скорости для подсетей источников, применяются поверх пользовательских ограничений. Explicit CIDR-правила используют longest-prefix-wins и имеют приоритет над auto-template. Auto-template создают bucket’ы лениво по matched source subnet: `*4/N` для IPv4, `*6/N` для IPv6, а `*/N` является dual-stack shorthand, где IPv4 использует `/N`, а IPv6 — `/(N * 4)`. - **Описание**: Лимиты скорости для подсетей источников, применяются поверх пользовательских ограничений. Explicit CIDR-правила используют longest-prefix-wins и имеют приоритет над auto-template. Auto-template создают bucket’ы лениво по matched source subnet: `*4/N` для IPv4, `*6/N` для IPv6, а `*/N` является dual-stack shorthand, где IPv4 использует `/N`, а IPv6 — `/(N * 4)`.
- **Example**: - **Example**:
@@ -3599,7 +3749,8 @@ Hostname нормализуется при валидации и должен п
[[upstreams]] [[upstreams]]
type = "socks5" type = "socks5"
address = "203.0.113.10:1080" address = "203.0.113.10:1080"
interface = "192.0.2.10" # explicit local bind IP # Use an explicit local bind IP.
interface = "192.0.2.10"
``` ```
## bind_addresses ## bind_addresses
- **Ограничения / валидация**: `String[]` (необязательный параметр). Применяется в случае, если `type = "direct"`. - **Ограничения / валидация**: `String[]` (необязательный параметр). Применяется в случае, если `type = "direct"`.
+55 -20
View File
@@ -42,8 +42,8 @@ that does not occur in modern browsers.
> TLS fingerprint has been fixed in latest version of clients for Desktop / Android / iOS. > TLS fingerprint has been fixed in latest version of clients for Desktop / Android / iOS.
> Please update your client for MTProxy Fake-TLS to work correctly. > Please update your client for MTProxy Fake-TLS to work correctly.
- We consider this a breakthrough aspect, which has no stable analogues today - For investigations based on JA4 ClientHello, see the [Telemt JA3/JA4 analysis guide](Architecture/Fronting-splitting/TLS_JA3_JA4_ANALYSIS.ru.md) (currently available in Russian).
- Based on this: if `telemt` configured correctly, **TLS mode is completely identical to real-life handshake + communication** with a specified host - Correctly configured TLS fronting preserves the real upstream TLS handshake and response path for unauthenticated traffic. Fingerprint resistance still depends on the client version, selected host, network path, and external validation.
- Here is our evidence: - Here is our evidence:
- 212.220.88.77 - "dummy" host, running `telemt` - 212.220.88.77 - "dummy" host, running `telemt`
- `petrovich.ru` - `tls` + `masking` host, in HEX: `706574726f766963682e7275` - `petrovich.ru` - `tls` + `masking` host, in HEX: `706574726f766963682e7275`
@@ -59,7 +59,10 @@ that does not occur in modern browsers.
- with original handshake - with original handshake
- with full request-response way - with full request-response way
- with low-latency overhead - with low-latency overhead
```bash > [!NOTE]
> The following capture is historical evidence from January 1, 2026, not a live availability check. Its displayed certificate expired on March 1, 2026; validate the current endpoint and certificate independently.
```text
root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/ root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/
* Added petrovich.ru:443:212.220.88.77 to DNS cache * Added petrovich.ru:443:212.220.88.77 to DNS cache
* Hostname petrovich.ru was found in DNS cache * Hostname petrovich.ru was found in DNS cache
@@ -175,6 +178,24 @@ Those cross-DC requests are normal and happen constantly.
This is also why it is required for MTProxy to reach Telegram's DC infrastructure as a whole. This is also why it is required for MTProxy to reach Telegram's DC infrastructure as a whole.
The proxy itself doesn't care which DC your account lives on. The client negotiates the correct DC through the proxy after connecting. The proxy itself doesn't care which DC your account lives on. The client negotiates the correct DC through the proxy after connecting.
### What do `dd` and `ee` mean in MTProxy?
They select different proxy modes and appear at the start of the encoded secret. `dd` enables the secure obfuscated transport. `ee` enables Fake TLS and appends the configured SNI domain to the secret. Choose between them according to client support, the censorship environment, and the configured fronting/masking path. Use `ee` only when TLS-shaped traffic is required and validate it through the real public endpoint; WEB mode supports `plain` and `dd`, not `ee`.
### Where are these modes configured?
Configure the modes in `[general.modes]`:
```toml
[general.modes]
# Classic MTProxy mode.
classic = false
# dd mode.
secure = false
# ee Fake TLS mode.
tls = true
```
### How many people can use one link ### How many people can use one link
By default, an unlimited number of people can use a single link. By default, an unlimited number of people can use a single link.
However, you can limit the number of unique IP addresses for each user: However, you can limit the number of unique IP addresses for each user:
@@ -223,17 +244,19 @@ This does not recover stale clients, but it makes port 443 wire-indistinguishabl
2. Add the following parameters: 2. Add the following parameters:
```toml ```toml
[server] [server]
metrics_port = 9090 metrics_listen = "127.0.0.1:9090"
metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"] metrics_whitelist = ["127.0.0.1/32", "::1/128"]
``` ```
3. Save the changes (Ctrl+S -> Ctrl+X). 3. Save the changes (Ctrl+S -> Ctrl+X).
4. After that, metrics will be available at: `SERVER_IP:9090/metrics`. 4. Metrics will be available locally at `http://127.0.0.1:9090/metrics`.
> [!WARNING] > [!WARNING]
> The value `"0.0.0.0/0"` in `metrics_whitelist` opens access to metrics from any IP address. It is recommended to replace it with your personal IP, for example: `"1.2.3.4/32"`. > Keep metrics on loopback unless a remote collector is required. For remote collection, bind an explicit private address, whitelist only the collector CIDR, and enforce the same boundary in the host firewall. Never expose metrics with a `/0` whitelist.
For load-related counters and operating-system checks, see the [high-load guide](Advanced_settings/HIGH_LOAD.en.md#5-diagnostics--monitoring).
### Too many open files ### Too many open files
- On a fresh Linux install the default open file limit is low; under load `telemt` may fail with `Accept error: Too many open files` - On a fresh Linux install the default open file limit is low; under load `telemt` may fail with `Accept error: Too many open files`
- **Systemd**: add `LimitNOFILE=65536` to the `[Service]` section (already included in the example above) - **Systemd**: add `LimitNOFILE=65536` to the `[Service]` section.
- **Docker**: add `--ulimit nofile=65536:65536` to your `docker run` command, or in `docker-compose.yml`: - **Docker**: add `--ulimit nofile=65536:65536` to your `docker run` command, or in `docker-compose.yml`:
```yaml ```yaml
ulimits: ulimits:
@@ -242,7 +265,7 @@ ulimits:
hard: 65536 hard: 65536
``` ```
- **System-wide** (optional): add to `/etc/security/limits.conf`: - **System-wide** (optional): add to `/etc/security/limits.conf`:
``` ```conf
* soft nofile 1048576 * soft nofile 1048576
* hard nofile 1048576 * hard nofile 1048576
root soft nofile 1048576 root soft nofile 1048576
@@ -253,17 +276,20 @@ root hard nofile 1048576
## Additional parameters ## Additional parameters
### Domain in the link instead of IP ### Domain in the link instead of IP
To display a domain instead of an IP address in the connection links, add the following lines to the configuration file: To display a domain instead of an IP address in native `tg://proxy` links, add the following lines to the configuration file:
```toml ```toml
[general.links] [general.links]
public_host = "proxy.example.com" public_host = "proxy.example.com"
``` ```
This setting, together with `public_port`, affects only native links. WEB `tg://webproxy` links always use `[[web.vhosts]].host` and external port `443`.
### Total server connection limit ### Total server connection limit
This parameter limits the total number of active connections to the server: This parameter limits the total number of active connections to the server:
```toml ```toml
[server] [server]
max_connections = 10000 # 0 - unlimited, 10000 - default # Zero disables the limit; 10000 is the default.
max_connections = 10000
``` ```
### Upstream Manager ### Upstream Manager
@@ -275,27 +301,36 @@ To configure outbound connections (upstreams), add the corresponding parameters
type = "direct" type = "direct"
weight = 1 weight = 1
enabled = true enabled = true
interface = "192.168.1.100" # Replace with your outbound IP # Replace this value with your outbound IP.
interface = "192.168.1.100"
``` ```
#### Using SOCKS4/5 as an Upstream #### Using SOCKS4/5 as an Upstream
- Without authorization: - Without authorization:
```toml ```toml
[[upstreams]] [[upstreams]]
type = "socks5" # Specify SOCKS4 or SOCKS5 # Specify SOCKS4 or SOCKS5.
address = "1.2.3.4:1234" # SOCKS-server Address type = "socks5"
weight = 1 # Set Weight for Scenarios # SOCKS server address.
address = "1.2.3.4:1234"
# Selection weight.
weight = 1
enabled = true enabled = true
``` ```
- With authorization: - With authorization:
```toml ```toml
[[upstreams]] [[upstreams]]
type = "socks5" # Specify SOCKS4 or SOCKS5 # Specify SOCKS4 or SOCKS5.
address = "1.2.3.4:1234" # SOCKS-server Address type = "socks5"
username = "user" # Username for Auth on SOCKS-server # SOCKS server address.
password = "pass" # Password for Auth on SOCKS-server address = "1.2.3.4:1234"
weight = 1 # Set Weight for Scenarios # SOCKS username.
username = "user"
# SOCKS password.
password = "pass"
# Selection weight.
weight = 1
enabled = true enabled = true
``` ```
+72 -34
View File
@@ -42,8 +42,7 @@ hello2 = "ad_tag2"
- Для расследования блокировок на базе JA4 ClientHello используйте отдельную инструкцию: [`JA3 и JA4 анализ в Telemt`](Architecture/Fronting-splitting/TLS_JA3_JA4_ANALYSIS.ru.md). - Для расследования блокировок на базе JA4 ClientHello используйте отдельную инструкцию: [`JA3 и JA4 анализ в Telemt`](Architecture/Fronting-splitting/TLS_JA3_JA4_ANALYSIS.ru.md).
- Мы считаем это прорывом, которому на сегодняшний день нет стабильных аналогов; - При корректной настройке TLS fronting неаутентифицированный трафик проходит через реальный upstream TLS handshake и получает его ответы. Устойчивость fingerprint по-прежнему зависит от версии клиента, выбранного host, сетевого пути и внешней проверки;
- Исходя из этого: если `telemt` настроен правильно, **режим TLS полностью идентичен реальному «рукопожатию» + обмену данными** с указанным хостом;
- Вот наши доказательства: - Вот наши доказательства:
- 212.220.88.77 — «фиктивный» хост, на котором запущен `telemt`; - 212.220.88.77 — «фиктивный» хост, на котором запущен `telemt`;
- `petrovich.ru` — хост с `tls` + `masking`, в HEX: `706574726f766963682e7275`; - `petrovich.ru` — хост с `tls` + `masking`, в HEX: `706574726f766963682e7275`;
@@ -61,7 +60,10 @@ hello2 = "ad_tag2"
- с полным циклом запрос-ответ; - с полным циклом запрос-ответ;
- с низкой задержкой. - с низкой задержкой.
```bash > [!NOTE]
> Ниже приведён исторический capture от 1 января 2026 года, а не проверка текущей доступности. Показанный сертификат истёк 1 марта 2026 года; актуальные endpoint и сертификат необходимо проверять отдельно.
```text
root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/ root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/
* Added petrovich.ru:443:212.220.88.77 to DNS cache * Added petrovich.ru:443:212.220.88.77 to DNS cache
* Hostname petrovich.ru was found in DNS cache * Hostname petrovich.ru was found in DNS cache
@@ -130,6 +132,8 @@ Keep-Alive: timeout=60
- Мы поставили перед собой задачу, не сдавались и не просто «бились в пустоту»: теперь у нас есть что вам показать. - Мы поставили перед собой задачу, не сдавались и не просто «бились в пустоту»: теперь у нас есть что вам показать.
- Не верите нам на слово? — Это прекрасно, и мы уважаем ваше решение: вы можете собрать свой собственный `telemt` или скачать готовую сборку и проверить её прямо сейчас. - Не верите нам на слово? — Это прекрасно, и мы уважаем ваше решение: вы можете собрать свой собственный `telemt` или скачать готовую сборку и проверить её прямо сейчас.
## ЧаВо
### Звонки в Telegram через MTProxy ### Звонки в Telegram через MTProxy
- Архитектура Telegram **НЕ поддерживает звонки через MTProxy**, а только через SOCKS5, который невозможно замаскировать - Архитектура Telegram **НЕ поддерживает звонки через MTProxy**, а только через SOCKS5, который невозможно замаскировать
@@ -154,7 +158,7 @@ Keep-Alive: timeout=60
- в Иране во время «активности». - в Иране во время «активности».
## Зачем нужен middle proxy (ME) ### Зачем нужен middle proxy (ME)
https://github.com/telemt/telemt/discussions/167 https://github.com/telemt/telemt/discussions/167
## Как клиенты взаимодействуют с дата-центрами Telegram ## Как клиенты взаимодействуют с дата-центрами Telegram
@@ -175,25 +179,23 @@ Telegram заранее определяет к какому DC привязат
По той же причине MTProxy необходимо иметь доступ к инфраструктуре Telegram целиком, а не частично. По той же причине MTProxy необходимо иметь доступ к инфраструктуре Telegram целиком, а не частично.
Cамому MTProxy всё равно, на каком DC живёт ваш аккаунт. Клиент cам договаривается о нужном DC через прокси уже после подключения. Cамому MTProxy всё равно, на каком DC живёт ваш аккаунт. Клиент cам договаривается о нужном DC через прокси уже после подключения.
## Что такое dd и ee в контексте MTProxy? ### Что такое `dd` и `ee` в контексте MTProxy?
Это два разных режима работы прокси. Понять, какой режим используется, можно взглянув на начало секрета — там будет dd или ee, вот пример: Это разные режимы прокси, обозначаемые в начале закодированного секрета. `dd` включает защищённый обфусцированный транспорт. `ee` включает Fake TLS и добавляет к секрету настроенный SNI-домен. Выбирайте режим по поддержке клиентом, условиям цензуры и настроенному пути fronting/masking. Используйте `ee`, только когда требуется TLS-shaped traffic, и проверяйте его через реальный публичный endpoint; WEB-режим поддерживает `plain` и `dd`, но не `ee`.
tg://proxy?server=s1.dimasssss.space&port=443&secret=eebe3007e927acd147dde12bee8b1a7c9364726976652e676f6f676c652e636f6d
dd — режим с мусорным трафиком, обфускацией данных, похожий на shadowsocks. У такого трафика есть заметный паттерн, который DPI умеют распознавать и впоследствии блокировать. Использовать этот режим на текущий момент не рекомендуется.
ee — режим маскировки под существующий домен (FakeTLS), словно вы сёрфите в интернете через браузер. На текущий момент не попадает под блокировку.
### Где эти режимы настраиваются? ### Где эти режимы настраиваются?
```toml ```toml
В конфиге telemt.toml в разделе [general.modes]: [general.modes]
classic = false # классический режим, давно стал бесполезным # Classic MTProxy mode.
secure = false # переменная dd-режима classic = false
tls = true # переменная ee-режима # dd mode.
secure = false
# ee Fake TLS mode.
tls = true
``` ```
## Сколько человек может пользоваться одной ссылкой ### Сколько человек может пользоваться одной ссылкой
По умолчанию одной ссылкой может пользоваться неограниченное число людей. По умолчанию одной ссылкой может пользоваться неограниченное число людей.
Однако вы можете ограничить количество уникальных IP-адресов для каждого пользователя: Однако вы можете ограничить количество уникальных IP-адресов для каждого пользователя:
@@ -203,7 +205,7 @@ hello = 1
``` ```
Этот параметр задает максимальное количество уникальных IP-адресов, с которых можно одновременно использовать одну ссылку. Если первый пользователь отключится, второй сможет подключиться. При этом с одного IP-адреса могут подключаться несколько пользователей одновременно (например, устройства в одной Wi-Fi сети). Этот параметр задает максимальное количество уникальных IP-адресов, с которых можно одновременно использовать одну ссылку. Если первый пользователь отключится, второй сможет подключиться. При этом с одного IP-адреса могут подключаться несколько пользователей одновременно (например, устройства в одной Wi-Fi сети).
## Как создать несколько разных ссылок ### Как создать несколько разных ссылок
1. Сгенерируйте необходимое количество секретов с помощью команды: `openssl rand -hex 16`. 1. Сгенерируйте необходимое количество секретов с помощью команды: `openssl rand -hex 16`.
2. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`. 2. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`.
@@ -220,7 +222,7 @@ user3 = "00000000000000000000000000000003"
curl -s http://127.0.0.1:9091/v1/users | jq curl -s http://127.0.0.1:9091/v1/users | jq
``` ```
## Ошибка "Unknown TLS SNI" ### Ошибка "Unknown TLS SNI"
Обычно эта ошибка возникает, если вы изменили параметр `tls_domain`, но пользователи продолжают подключаться по старым ссылкам с прежним доменом. Обычно эта ошибка возникает, если вы изменили параметр `tls_domain`, но пользователи продолжают подключаться по старым ссылкам с прежним доменом.
Если необходимо разрешить подключение с любыми доменами (игнорируя несовпадения SNI), добавьте следующие параметры: Если необходимо разрешить подключение с любыми доменами (игнорируя несовпадения SNI), добавьте следующие параметры:
@@ -236,34 +238,61 @@ unknown_sni_action = "reject_handshake"
``` ```
Это не пропускает старых клиентов, но делает поведение на 443-м порту неотличимым от стокового веб-сервера, у которого просто нет такого виртуального хоста. Это не пропускает старых клиентов, но делает поведение на 443-м порту неотличимым от стокового веб-сервера, у которого просто нет такого виртуального хоста.
## Как посмотреть метрики ### Как посмотреть метрики
1. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`. 1. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`.
2. Добавьте следующие параметры: 2. Добавьте следующие параметры:
```toml ```toml
[server] [server]
metrics_port = 9090 metrics_listen = "127.0.0.1:9090"
metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"] metrics_whitelist = ["127.0.0.1/32", "::1/128"]
``` ```
3. Сохраните изменения (Ctrl+S -> Ctrl+X). 3. Сохраните изменения (Ctrl+S -> Ctrl+X).
4. После этого метрики будут доступны по адресу: `SERVER_IP:9090/metrics`. 4. Метрики будут доступны локально по адресу `http://127.0.0.1:9090/metrics`.
> [!WARNING] > [!WARNING]
> Значение `"0.0.0.0/0"` в `metrics_whitelist` открывает доступ к метрикам с любого IP-адреса. Рекомендуется заменить его на ваш личный IP, например: `"1.2.3.4/32"`. > Оставляйте metrics на loopback, если удалённый сборщик не требуется. Для удалённого сбора привяжите явный приватный адрес, разрешите только CIDR сборщика и закрепите ту же границу в host firewall. Не используйте whitelist `/0`.
Счётчики нагрузки и проверки операционной системы описаны в [руководстве по High-Load](Advanced_settings/HIGH_LOAD.ru.md#5-диагностика-и-мониторинг).
### Слишком много открытых файлов
- На свежей Linux-системе лимит открытых файлов обычно мал; под нагрузкой Telemt может завершать accept с ошибкой `Too many open files`.
- Для systemd добавьте `LimitNOFILE=65536` в секцию `[Service]`.
- Для Docker добавьте `--ulimit nofile=65536:65536` в `docker run` либо настройте Compose:
```yaml
ulimits:
nofile:
soft: 65536
hard: 65536
```
- При необходимости задайте системные пределы в `/etc/security/limits.conf`:
```conf
* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
```
## Дополнительные параметры ## Дополнительные параметры
### Домен в ссылке вместо IP ### Домен в ссылке вместо IP
Чтобы в ссылках для подключения отображался домен вместо IP-адреса, добавьте следующие строки в файл конфигурации: Чтобы в native-ссылках `tg://proxy` отображался домен вместо IP-адреса, добавьте следующие строки в файл конфигурации:
```toml ```toml
[general.links] [general.links]
public_host = "proxy.example.com" public_host = "proxy.example.com"
``` ```
Эта настройка вместе с `public_port` влияет только на native-ссылки. WEB-ссылки `tg://webproxy` всегда используют `[[web.vhosts]].host` и внешний порт `443`.
### Общий лимит подключений к серверу ### Общий лимит подключений к серверу
Этот параметр ограничивает общее количество активных подключений к серверу: Этот параметр ограничивает общее количество активных подключений к серверу:
```toml ```toml
[server] [server]
max_connections = 10000 # 0 - без ограничений, 10000 - по умолчанию # Zero disables the limit; 10000 is the default.
max_connections = 10000
``` ```
### Upstream Manager ### Upstream Manager
@@ -275,27 +304,36 @@ max_connections = 10000 # 0 - без ограничений, 10000 - по у
type = "direct" type = "direct"
weight = 1 weight = 1
enabled = true enabled = true
interface = "192.168.1.100" # Замените на ваш исходящий IP # Replace this value with your outbound IP.
interface = "192.168.1.100"
``` ```
#### Использование SOCKS4/5 в качестве Upstream #### Использование SOCKS4/5 в качестве Upstream
- Без авторизации: - Без авторизации:
```toml ```toml
[[upstreams]] [[upstreams]]
type = "socks5" # выбор типа SOCKS4 или SOCKS5 # Specify SOCKS4 or SOCKS5.
address = "1.2.3.4:1234" # адрес сервера SOCKS type = "socks5"
weight = 1 # вес # SOCKS server address.
address = "1.2.3.4:1234"
# Selection weight.
weight = 1
enabled = true enabled = true
``` ```
- С авторизацией: - С авторизацией:
```toml ```toml
[[upstreams]] [[upstreams]]
type = "socks5" # выбор типа SOCKS4 или SOCKS5 # Specify SOCKS4 or SOCKS5.
address = "1.2.3.4:1234" # адрес сервера SOCKS type = "socks5"
username = "user" # имя пользователя # SOCKS server address.
password = "pass" # пароль address = "1.2.3.4:1234"
weight = 1 # вес # SOCKS username.
username = "user"
# SOCKS password.
password = "pass"
# Selection weight.
weight = 1
enabled = true enabled = true
``` ```
+48 -41
View File
@@ -13,6 +13,7 @@ curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
After starting, the script will prompt for: After starting, the script will prompt for:
- Your language (1 - English, 2 - Russian); - Your language (1 - English, 2 - Russian);
- Your server port (press Enter for 443);
- Your TLS domain (press Enter for petrovich.ru). - Your TLS domain (press Enter for petrovich.ru).
The script checks if the port (default **443**) is free. If the port is already in use, installation will fail. You need to free up the port or use the **-p** flag with a different port to retry the installation. The script checks if the port (default **443**) is free. If the port is already in use, installation will fail. You need to free up the port or use the **-p** flag with a different port to retry the installation.
@@ -22,7 +23,7 @@ To modify the script’s startup parameters, you can use the following flags:
- **-p, --port** - server port (1–65535); - **-p, --port** - server port (1–65535);
- **-s, --secret** - 32 hex secret; - **-s, --secret** - 32 hex secret;
- **-a, --ad-tag** - ad_tag; - **-a, --ad-tag** - ad_tag;
- **-l, --lan**g - language (1/en or 2/ru); - **-l, --lang** - language (1/en or 2/ru);
Providing all options skips interactive prompts. Providing all options skips interactive prompts.
@@ -33,7 +34,8 @@ tg://proxy?server=IP&port=PORT&secret=SECRET
### Installing a specific version ### Installing a specific version
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- 3.3.39 TELEMT_VERSION=3.5.7
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- "$TELEMT_VERSION"
``` ```
### Uninstall with full cleanup ### Uninstall with full cleanup
@@ -64,7 +66,7 @@ chmod +x /bin/telemt
**This guide "assumes" that you:** **This guide "assumes" that you:**
- logged in as root or executed `su -` / `sudo su` - logged in as root or executed `su -` / `sudo su`
- Already have the "telemt" executable file in the /bin folder. Read the **[Installation](#Installation)** section. - Already have the "telemt" executable file in the /bin folder. Read the **[Installation](#installation)** section.
--- ---
@@ -105,18 +107,18 @@ nano /etc/telemt/telemt.toml
Insert your configuration: Insert your configuration:
```toml ```toml
### Telemt Based Config.toml # Minimal Telemt configuration
# We believe that these settings are sufficient for most scenarios # These settings are sufficient for most deployments that do not require
# where cutting-egde methods and parameters or special solutions are not needed # advanced methods, parameters, or specialized solutions.
# === General Settings === # General settings
[general] [general]
use_middle_proxy = true use_middle_proxy = true
# Global ad_tag fallback when user has no per-user tag in [access.user_ad_tags] # Global ad_tag fallback when user has no per-user tag in [access.user_ad_tags]
# ad_tag = "00000000000000000000000000000000" # ad_tag = "00000000000000000000000000000000"
# Per-user ad_tag in [access.user_ad_tags] (32 hex from @MTProxybot) # Per-user ad_tag in [access.user_ad_tags] (32 hex from @MTProxybot)
# === Log Level === # Logging
# Log level: debug | verbose | normal | silent # Log level: debug | verbose | normal | silent
# Can be overridden with --silent or --log-level CLI flags # Can be overridden with --silent or --log-level CLI flags
# RUST_LOG env var takes absolute priority over all of these # RUST_LOG env var takes absolute priority over all of these
@@ -129,17 +131,23 @@ tls = true
[general.links] [general.links]
show = "*" show = "*"
# show = ["alice", "bob"] # Only show links for alice and bob # Only show links for alice and bob
# show = "*" # Show links for all users # show = ["alice", "bob"]
# public_host = "proxy.example.com" # Host (IP or domain) for tg:// links # Show links for all users
# public_port = 443 # Port for tg:// links (default: server.port) # show = "*"
# Host (IP or domain) for tg:// links
# public_host = "proxy.example.com"
# Port for tg:// links; defaults to server.port
# public_port = 443
# === Server Binding === # Server binding
[server] [server]
port = 443 port = 443
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol # Enable behind HAProxy/nginx with PROXY protocol
# proxy_protocol = false
# metrics_port = 9090 # metrics_port = 9090
# metrics_listen = "127.0.0.1:9090" # Listen address for metrics (overrides metrics_port) # Listen address for metrics; overrides metrics_port
# metrics_listen = "127.0.0.1:9090"
# metrics_whitelist = ["127.0.0.1/32", "::1/128"] # metrics_whitelist = ["127.0.0.1/32", "::1/128"]
[server.api] [server.api]
@@ -153,12 +161,15 @@ minimal_runtime_cache_ttl_ms = 1000
[[server.listeners]] [[server.listeners]]
ip = "0.0.0.0" ip = "0.0.0.0"
# === Anti-Censorship & Masking === # Anti-censorship and masking
[censorship] [censorship]
tls_domain = "petrovich.ru" # Fake-TLS / SNI masking domain used in generated ee-links # Fake-TLS/SNI masking domain used in generated ee links.
tls_domain = "petrovich.ru"
mask = true mask = true
tls_emulation = true # Fetch real cert lengths and emulate TLS records # Fetch real certificate lengths and emulate TLS records.
tls_front_dir = "tlsfront" # Cache directory for TLS emulation tls_emulation = true
# Cache directory for TLS emulation.
tls_front_dir = "tlsfront"
[access.users] [access.users]
# format: "username" = "32_hex_chars_secret" # format: "username" = "32_hex_chars_secret"
@@ -237,8 +248,12 @@ curl -s http://127.0.0.1:9091/v1/users | jq -r '.data[] | "[\(.username)]", (.li
# Telemt via Docker Compose # Telemt via Docker Compose
**1. Edit `config.toml` in repo root (at least: port, users secrets, tls_domain)** **1. Create `config/` in the repository root and place the edited `config.toml` there (at least: port, user secrets, and `tls_domain`):**
**2. Start container:** ```bash
mkdir -p config
mv config.toml config/
```
**2. Start the container:**
```bash ```bash
docker compose up -d --build docker compose up -d --build
``` ```
@@ -251,34 +266,26 @@ docker compose logs -f telemt
docker compose down docker compose down
``` ```
> [!NOTE] > [!NOTE]
> - `docker-compose.yml` maps `./config.toml` to `/app/config.toml` (read-only) > - `docker-compose.yml` mounts `./config/` at `/etc/telemt/` read-write and starts Telemt with `/etc/telemt/config.toml`.
> - By default it publishes `443:443` and runs with dropped capabilities (only `NET_BIND_SERVICE` is added) > - The directory mount is required for mutating Control API endpoints: Telemt persists the complete configuration source graph with same-directory temporary files and atomic renames. Do not replace it with a single-file bind mount.
> - If you really need host networking (usually only for some IPv6 setups) uncomment `network_mode: host` > - The host `./config/` directory and its source files must be writable by the container user (UID/GID `65532` in the production image) when configuration mutations are enabled.
> - If you enable mutating Control API endpoints, mount a writable config directory instead of a single `config.toml` file. Telemt persists config changes with atomic `tmp + rename` writes, and a single bind-mounted file can fail with `Device or resource busy`. > - `/run/telemt` is a small writable `tmpfs`; the rest of the container filesystem remains read-only.
> - By default only `443:443` is public. The published Metrics and Control API ports are restricted to host loopback, and all capabilities except `NET_BIND_SERVICE` are dropped.
Example writable config mount for Control API mutations: > - Port publishing does not enable a service or make a container-loopback listener reachable. The bundled `config.toml` leaves Metrics disabled and binds the Control API to `127.0.0.1` inside the container. To use either host mapping, explicitly bind that service to a container-reachable address and whitelist only the immediate Docker peer/network; keep the host-side mapping on loopback.
```yaml
services:
telemt:
working_dir: /run/telemt
volumes:
- ./config:/etc/telemt:rw
tmpfs:
- /run/telemt:rw,mode=1777,size=4m
command: /usr/local/bin/telemt /etc/telemt/config.toml
```
**Run without Compose** **Run without Compose**
```bash ```bash
docker build -t telemt:local . docker build -t telemt:local .
docker run --name telemt --restart unless-stopped \ docker run --name telemt --restart unless-stopped \
-p 443:443 \ -p 443:443 \
-p 9090:9090 \ -p 127.0.0.1:9090:9090 \
-p 9091:9091 \ -p 127.0.0.1:9091:9091 \
-e RUST_LOG=info \ -e RUST_LOG=info \
-v "$PWD/config.toml:/app/config.toml:ro" \ -v "$PWD/config:/etc/telemt:rw" \
--tmpfs /run/telemt:rw,mode=1777,size=4m \
-w /run/telemt \
--read-only \ --read-only \
--cap-drop ALL --cap-add NET_BIND_SERVICE \ --cap-drop ALL --cap-add NET_BIND_SERVICE \
--ulimit nofile=65536:65536 \ --ulimit nofile=65536:65536 \
telemt:local telemt:local /etc/telemt/config.toml
``` ```
+51 -34
View File
@@ -12,6 +12,7 @@ curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
``` ```
После запуска скрипт запросит: После запуска скрипт запросит:
- ваш язык (1 - English, 2 - Русский); - ваш язык (1 - English, 2 - Русский);
- порт сервера (нажмите Enter для 443);
- ваш TLS-домен (нажмите Enter для petrovich.ru). - ваш TLS-домен (нажмите Enter для petrovich.ru).
Во время установки скрипт проверяет, свободен ли порт (по умолчанию **443**). Если порт занят другим процессом - установка завершится с ошибкой. Для повторной установки необходимо освободить порт или указать другой через флаг **-p**. Во время установки скрипт проверяет, свободен ли порт (по умолчанию **443**). Если порт занят другим процессом - установка завершится с ошибкой. Для повторной установки необходимо освободить порт или указать другой через флаг **-p**.
@@ -23,7 +24,7 @@ curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
- **-a, --ad-tag** - ad_tag; - **-a, --ad-tag** - ad_tag;
- **-l, --lang** - язык (1/en или 2/ru). - **-l, --lang** - язык (1/en или 2/ru).
Если заданы флаги для языка и домена, интерактивных вопросов не будет. Если заданы все параметры, интерактивных вопросов не будет.
После завершения установки скрипт выдаст ссылку для подключения клиентов: После завершения установки скрипт выдаст ссылку для подключения клиентов:
```bash ```bash
@@ -32,7 +33,8 @@ tg://proxy?server=IP&port=PORT&secret=SECRET
### Установка нужной версии ### Установка нужной версии
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- 3.3.39 TELEMT_VERSION=3.5.7
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- "$TELEMT_VERSION"
``` ```
### Удаление с полной очисткой ### Удаление с полной очисткой
@@ -104,21 +106,21 @@ nano /etc/telemt/telemt.toml
Вставьте свою конфигурацию Вставьте свою конфигурацию
```toml ```toml
### Конфигурационный файл на основе Telemt # Minimal Telemt configuration
# Мы полагаем, что этих настроек достаточно для большинства сценариев,  # These settings are sufficient for most deployments that do not require
# где не требуются передовые методы, параметры или специальные решения # advanced methods, parameters, or specialized solutions.
# === Общие настройки === # General settings
[general] [general]
use_middle_proxy = true use_middle_proxy = true
# Глобальный ad_tag, если у пользователя нет индивидуального тега в [access.user_ad_tags] # Global ad_tag fallback when user has no per-user tag in [access.user_ad_tags]
# ad_tag = "00000000000000000000000000000000" # ad_tag = "00000000000000000000000000000000"
# Индивидуальный ad_tag в [access.user_ad_tags] (32 шестнадцатеричных символа от @MTProxybot) # Per-user ad_tag in [access.user_ad_tags] (32 hex from @MTProxybot)
# === Уровень логирования === # Logging
# Уровень логирования: debug | verbose | normal | silent # Log level: debug | verbose | normal | silent
# Можно переопределить с помощью флагов командной строки --silent или --log-level # Can be overridden with --silent or --log-level CLI flags
# Переменная окружения RUST_LOG имеет абсолютный приоритет над всеми этими настройками # RUST_LOG env var takes absolute priority over all of these
log_level = "normal" log_level = "normal"
[general.modes] [general.modes]
@@ -128,17 +130,23 @@ tls = true
[general.links] [general.links]
show = "*" show = "*"
# show = ["alice", "bob"] # Показывать ссылки только для alice и bob # Only show links for alice and bob
# show = "*" # Показывать ссылки для всех пользователей # show = ["alice", "bob"]
# public_host = "proxy.example.com" # Хост (IP-адрес или домен) для ссылок tg:// # Show links for all users
# public_port = 443 # Порт для ссылок tg:// (по умолчанию: server.port) # show = "*"
# Host (IP or domain) for tg:// links
# public_host = "proxy.example.com"
# Port for tg:// links; defaults to server.port
# public_port = 443
# === Привязка сервера === # Server binding
[server] [server]
port = 443 port = 443
# proxy_protocol = false # Включите, если сервер находится за HAProxy/nginx с протоколом PROXY # Enable behind HAProxy/nginx with PROXY protocol
# proxy_protocol = false
# metrics_port = 9090 # metrics_port = 9090
# metrics_listen = "127.0.0.1:9090" # Адрес прослушивания для метрик (переопределяет metrics_port) # Listen address for metrics; overrides metrics_port
# metrics_listen = "127.0.0.1:9090"
# metrics_whitelist = ["127.0.0.1/32", "::1/128"] # metrics_whitelist = ["127.0.0.1/32", "::1/128"]
[server.api] [server.api]
@@ -148,19 +156,22 @@ whitelist = ["127.0.0.1/32", "::1/128"]
minimal_runtime_enabled = false minimal_runtime_enabled = false
minimal_runtime_cache_ttl_ms = 1000 minimal_runtime_cache_ttl_ms = 1000
# Прослушивание на нескольких интерфейсах/IP-адресах - IPv4 # Listen on multiple interfaces/IPs - IPv4
[[server.listeners]] [[server.listeners]]
ip = "0.0.0.0" ip = "0.0.0.0"
# === Обход блокировок и маскировка === # Anti-censorship and masking
[censorship] [censorship]
tls_domain = "petrovich.ru" # Домен Fake-TLS / SNI, который будет использоваться в сгенерированных ee-ссылках # Fake-TLS/SNI masking domain used in generated ee links.
tls_domain = "petrovich.ru"
mask = true mask = true
tls_emulation = true # Получить реальную длину сертификата и эмулировать запись TLS # Fetch real certificate lengths and emulate TLS records.
tls_front_dir = "tlsfront" # Директория кэша для эмуляции TLS tls_emulation = true
# Cache directory for TLS emulation.
tls_front_dir = "tlsfront"
[access.users] [access.users]
# формат: "имя_пользователя" = "секрет_из_32_шестнадцатеричных_символов" # format: "username" = "32_hex_chars_secret"
hello = "00000000000000000000000000000000" hello = "00000000000000000000000000000000"
``` ```
@@ -235,9 +246,10 @@ curl -s http://127.0.0.1:9091/v1/users | jq -r '.data[] | "[\(.username)]", (.li
# Telemt через Docker Compose # Telemt через Docker Compose
**1. Создайте директорию `config/` и поместите в неё отрдеактированный `config.toml` (указав как минимум: порт, пользовательские секреты, tls_domain):** **1. Создайте директорию `config/` и поместите в неё отредактированный `config.toml` (указав как минимум порт, пользовательские секреты и `tls_domain`):**
```bash ```bash
mkdir config && mv config.toml config/ mkdir -p config
mv config.toml config/
``` ```
**2. Запустите контейнер:** **2. Запустите контейнер:**
```bash ```bash
@@ -252,21 +264,26 @@ docker compose logs -f telemt
docker compose down docker compose down
``` ```
> [!NOTE] > [!NOTE]
> - Директория `./config/` монтируется в `/etc/telemt/` (read-write), что позволяет API атомарно обновлять config.toml > - `docker-compose.yml` монтирует `./config/` в `/etc/telemt/` с правом записи и запускает Telemt с `/etc/telemt/config.toml`.
> - По умолчанию публикуются порты 443:443, а контейнер запускается со сброшенными привилегиями (добавлена только `NET_BIND_SERVICE`) > - Монтирование директории необходимо для изменяющих Control API endpoints: Telemt сохраняет полный граф источников конфигурации через временные файлы в тех же директориях и атомарные rename. Не заменяйте его bind mount одного файла.
> - Если вам действительно нужна сеть хоста (обычно это требуется только для некоторых конфигураций IPv6), раскомментируйте `network_mode: host` > - Host-директория `./config/` и файлы источников должны быть доступны для записи пользователю контейнера (UID/GID `65532` в production image), если включены изменения конфигурации.
> - `/run/telemt` предоставляется как небольшой записываемый `tmpfs`; остальная файловая система контейнера остаётся read-only.
> - По умолчанию публично доступен только `443:443`. Опубликованные порты Metrics и Control API ограничены loopback хоста, а из capabilities оставлена только `NET_BIND_SERVICE`.
> - Публикация порта не включает сервис и не делает доступным listener, привязанный к loopback контейнера. В поставляемом `config.toml` Metrics выключены, а Control API привязан к `127.0.0.1` внутри контейнера. Чтобы использовать любой из host mappings, явно привяжите сервис к адресу, доступному из контейнерной сети, и добавьте в whitelist только непосредственный Docker peer/network; host-side mapping оставьте на loopback.
**Запуск без Docker Compose** **Запуск без Docker Compose**
```bash ```bash
docker build -t telemt:local . docker build -t telemt:local .
docker run --name telemt --restart unless-stopped \ docker run --name telemt --restart unless-stopped \
-p 443:443 \ -p 443:443 \
-p 9090:9090 \ -p 127.0.0.1:9090:9090 \
-p 9091:9091 \ -p 127.0.0.1:9091:9091 \
-e RUST_LOG=info \ -e RUST_LOG=info \
-v "$PWD/config.toml:/app/config.toml:ro" \ -v "$PWD/config:/etc/telemt:rw" \
--tmpfs /run/telemt:rw,mode=1777,size=4m \
-w /run/telemt \
--read-only \ --read-only \
--cap-drop ALL --cap-add NET_BIND_SERVICE \ --cap-drop ALL --cap-add NET_BIND_SERVICE \
--ulimit nofile=65536:65536 \ --ulimit nofile=65536:65536 \
telemt:local telemt:local /etc/telemt/config.toml
``` ```
+77 -18
View File
@@ -22,11 +22,23 @@ Telemt-WEB-Listener
`-- gewöhnlicher oder ungültiger Request --> konfigurierte Decoy-Site `-- gewöhnlicher oder ungültiger Request --> konfigurierte Decoy-Site
``` ```
Leiten Sie den vollständigen öffentlichen vhost an Telemt weiter. Wenn der TLS-Terminator nur bekannte Carrier-Pfade trennt, unterscheiden sich gewöhnliches und authentifiziertes Verhalten beobachtbar und Telemt kann seine Decoy-Richtlinie nicht durchsetzen. Leiten Sie den vollständigen konfigurierten WEB-Bereich an Telemt weiter. Beim standardmäßig leeren `base_path` ist dies der gesamte öffentliche vhost, andernfalls der exakte, mit einem Schrägstrich abgeschlossene Teilbaum. Wenn der TLS-Terminator innerhalb dieses Bereichs nur bekannte Carrier-Endpunkte trennt, unterscheiden sich gewöhnliches und authentifiziertes Verhalten beobachtbar und Telemt kann seine Decoy-Richtlinie nicht durchsetzen.
`BASE` bezeichnet `/` bei leerem `base_path`, andernfalls `/<base_path>/`. Die öffentlichen WEB-Routen sind relativ zu dieser exakten Basis:
| Methode | Pfad | Zweck |
| --- | --- | --- |
| `GET` | `BASE?bridge=<capability>` | Erstes Bridge-Dokument oder, mit Recovery-`Accept`-Header und optionalem Bearer, die Recovery-Repräsentation. |
| `POST`, `DELETE` | `BASEapi/v1/session` | Parent-Sitzung erstellen oder schließen. |
| `POST` | `BASEapi/v1/up` | Uplink des HTTPS-Carriers. |
| `POST` | `BASEapi/v1/down` | Downlink des HTTPS-Carriers. |
| `GET` | `BASEapi/v1/ws` | WebSocket-Upgrade. |
`POST BASEapi/v1/diagnostic` ist eine interne Sideband-Route der generierten Bridge und keine öffentliche Client-API. Das Routing ist groß-/kleinschreibungssensitiv und bytegenau: Es gibt keine Aliase, Varianten mit zusätzlichen oder percent-encoded Schrägstrichen und keine Query-Parameter an Carrier-Endpunkten. Ein falsch geformter Request mit einer vom aktuellen Prozess authentifizierten Capability oder einem solchen Bearer erhält lokal ein nicht cachebares `404`; ein nicht passender Request ohne authentisches Carrier-Material folgt dem konfigurierten Decoy. `base_path` ändert nur diese Routen des WEB-Listeners. Control API, `/web-status` und Prometheus-Metriken erhalten kein Präfix.
## Unterstützter Client-Vertrag ## Unterstützter Client-Vertrag
- Der öffentliche Endpunkt ist immer `https://HOST:443`. - Bei leerem `base_path` lautet der öffentliche Endpunkt `https://HOST:443/`, andernfalls `https://HOST:443/BASE/`. Der Basispfad ist groß-/kleinschreibungssensitiv und exakt; Telemt leitet ihn nicht um, normalisiert ihn nicht und entfernt ihn nicht vor der Decoy-Weiterleitung.
- Unterstützt werden 16-Byte-MTProxy-Secrets in den Modi `plain` und `dd`. FakeTLS-Secrets mit `ee` werden im WEB-Modus nicht unterstützt. - Unterstützt werden 16-Byte-MTProxy-Secrets in den Modi `plain` und `dd`. FakeTLS-Secrets mit `ee` werden im WEB-Modus nicht unterstützt.
- `web.carrier` wählt den einzigen Carrier bei deaktivierter Auto-Negotiation und den letzten Fallback bei aktivierter Negotiation. `https` verwendet serialisierte HTTPS-Uplinks und Long Polling. `https-lanes` verwendet unabhängige HTTPS-Sequenzen und Polls pro logischem Stream. `websocket` verwendet einen geordneten WebSocket für alle Streams. `websocket-lanes` verwendet einen unabhängig verwalteten WebSocket für jeden logischen Stream ungleich null. - `web.carrier` wählt den einzigen Carrier bei deaktivierter Auto-Negotiation und den letzten Fallback bei aktivierter Negotiation. `https` verwendet serialisierte HTTPS-Uplinks und Long Polling. `https-lanes` verwendet unabhängige HTTPS-Sequenzen und Polls pro logischem Stream. `websocket` verwendet einen geordneten WebSocket für alle Streams. `websocket-lanes` verwendet einen unabhängig verwalteten WebSocket für jeden logischen Stream ungleich null.
- Ein fehlendes `web.carriers` oder `web.carriers = false` deaktiviert Auto-Negotiation und Lernen. Ein nicht leeres Array aktiviert ausschließlich die sequenzielle Start-Negotiation; eine bereits festgeschriebene Sitzung wird nie migriert. - Ein fehlendes `web.carriers` oder `web.carriers = false` deaktiviert Auto-Negotiation und Lernen. Ein nicht leeres Array aktiviert ausschließlich die sequenzielle Start-Negotiation; eine bereits festgeschriebene Sitzung wird nie migriert.
@@ -40,9 +52,10 @@ Telegram-Desktop-WEB-Links enthalten keinen Port, da der Client Port 443 vorauss
```text ```text
tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef
tg://webproxy?server=proxy.example.com&secret=dd0123456789abcdef0123456789abcdef tg://webproxy?server=proxy.example.com&secret=dd0123456789abcdef0123456789abcdef
tg://webproxy?server=proxy.example.com%2Ftelegram%2Fweb&secret=cAABAgMEBQYHCAkKCwwNDg8
``` ```
Telemt gibt Links für die durch `[general.links].show` ausgewählten WEB-Profile über das vorhandene Log-Target `telemt::links` aus. Telemt gibt beim Prozessstart Links für die durch `[general.links].show` ausgewählten WEB-Profile über das vorhandene Log-Target `telemt::links` aus. Root-Links behalten das bisherige hexadezimale Secret. Bei einem Pfad-Link ist `HOST/BASE` im Parameter `server` percent-encoded; das Secret ist ungepolstertes base64url von `0x70 || client_secret`, wobei `client_secret` im Modus `plain` das rohe 16-Byte-Secret und im Modus `dd` den Wert `0xdd || secret` bezeichnet. Die Users-API liefert nur das rohe Secret und keinen WEB-Link. `[general.links].public_host` und `public_port` wirken nur auf native Links und überschreiben keine WEB-vhost-Links.
## Voraussetzungen ## Voraussetzungen
@@ -76,9 +89,12 @@ web_trusted_proxy_cidrs = ["127.0.0.1/32"]
[web] [web]
enabled = true enabled = true
carrier = "https-lanes" carrier = "https-lanes"
decoy_fasttrack_mode = "off"
http_connection_capacity_action = "drop"
[[web.vhosts]] [[web.vhosts]]
host = "proxy.example.com" host = "proxy.example.com"
base_path = "telegram/web"
public_addr = "203.0.113.10:443" public_addr = "203.0.113.10:443"
[web.vhosts.decoy] [web.vhosts.decoy]
@@ -93,6 +109,12 @@ max_streams = 512
max_streams_per_session = 64 max_streams_per_session = 64
``` ```
Die Behandlung überlasteter angenommener Sockets ist separat konfigurierbar. `drop` behält das bisherige Schließen nach `accept(2)` bei. `respond` schreibt ohne Request-Parsing eine leere, wiederholbare `503`-Antwort. `wait` wartet außerhalb der Accept-Schleife auf gewöhnliche Verbindungskapazität und wechselt danach in die normale HTTP-Verarbeitung; bei Timeout wird dieselbe `503` geschrieben. Warten und Schreiben verwenden pro Phase `web.timeouts.http_overload_timeout_ms`. `web.limits.max_http_overload_connections` begrenzt Sockets außerhalb der gewöhnlichen Kapazität und erfordert bei Änderung einen Prozessneustart; Aktion und Timeout sind hot-reload-fähig.
`base_path` ist standardmäßig leer. Ein nicht leerer Wert umfasst höchstens 128 ASCII-Bytes und besteht aus durch Schrägstriche getrennten Segmenten der Form `[A-Za-z0-9][A-Za-z0-9_-]*`, ohne führenden oder abschließenden Schrägstrich. Root-vhosts behalten die v1-Capability-Ableitung. Pfad-vhosts verwenden den v2-Kontext mit exakt kanonischem Host und Basispfad; eine Änderung der Groß-/Kleinschreibung oder eines Segments ändert daher sowohl Route als auch Capability.
`decoy_fasttrack_mode` steuert ausschließlich die Capability-Verarbeitung für `GET/HEAD` am konfigurierten Basis-Root. `off` ist der Default und behält den vollständigen bisherigen Scan ohne Fast-Track-Zähler bei. `shadow` erfasst, welche strukturell unmöglichen Requests den Scan umgehen könnten, führt ihn aber weiterhin vollständig aus. `enforce` umgeht Capability-Arbeit nur bei `HEAD` oder fehlender beziehungsweise nicht kanonischer `bridge`-Query. Jeder exakte kanonische Bridge-GET am Basis-Root scannt bei Treffer und Fehlschlag vollständig alle Profile des ausgewählten vhost. Die Einstellung erfordert einen Prozessneustart; Reload speichert den gewünschten Wert, meldet `web.decoy_fasttrack_mode` aber als zurückgestellt. Fast-Track schützt nicht vor gegnerischer CPU-Last, da ein Scanner stets kanonische Kandidaten senden kann; `enforce` kann außerdem eine öffentliche Timing-Klasse der Request-Form sichtbar machen, insbesondere bei einem statischen Decoy. Aktivieren Sie diesen Modus nicht ohne externe Timing-Messungen über den produktiven TLS-Terminator.
## Serverseitige Carrier-Negotiation ## Serverseitige Carrier-Negotiation
Auto-Negotiation ist optional und bleibt deaktiviert, solange `carriers` nicht als explizites, nicht leeres Array gesetzt ist. Der konfigurierte `carrier` bleibt der letzte Fallback und wird genau einmal angehängt, auch wenn er bereits im Array steht: Auto-Negotiation ist optional und bleibt deaktiviert, solange `carriers` nicht als explizites, nicht leeres Array gesetzt ist. Der konfigurierte `carrier` bleibt der letzte Fallback und wird genau einmal angehängt, auch wenn er bereits im Array steht:
@@ -111,20 +133,29 @@ carrier_health_secs = 30
carrier_learning_secs = 600 carrier_learning_secs = 600
bridge_request_secs = 10 bridge_request_secs = 10
bridge_retry_secs = 90 bridge_retry_secs = 90
bridge_recovery_secs = 15
carrier_probe_coalesce_ms = 0 carrier_probe_coalesce_ms = 0
``` ```
Die erzeugte Bridge sendet bei `/session` die kanonischen Header `X-Carrier-Capabilities`, `X-Carrier-Attempt` und ab dem zweiten Versuch `X-Carrier-Failure`. Jede erfolgreiche automatische Response liefert `X-Carrier-Mode`, `X-Carrier-Attempt`, `X-Carrier-Candidate-Count`, `X-Carrier-Deadline` und `X-Carrier-State`. Die Bridge startet ihre lokale kumulative Uhr unmittelbar vor dem ersten `/session`-Request; der Server friert seine separate absolute Chain-Deadline bei Annahme des ersten automatischen Versuchs ein. Beide verwenden die konfigurierten Offsets und werden bei Ersatzversuchen nicht zurückgesetzt. Für einen bis vier effektive Kandidaten lauten die Attempt-Checkpoints entsprechend `[d3]`, `[d0, d3]`, `[d0, d1, d3]` und `[d0, d1, d2, d3]`; der letzte Kandidat verwendet immer `d3`. Ein Nachfolger bleibt bis zu seinem eigenen Checkpoint zulässig. Die Zustände sind `provisional`, `committed` und `healthy`. Die erzeugte Bridge sendet bei `/session` die kanonischen Header `X-Carrier-Capabilities`, `X-Carrier-Attempt` und ab dem zweiten Versuch `X-Carrier-Failure`. Jede erfolgreiche automatische Response liefert `X-Carrier-Mode`, `X-Carrier-Attempt`, `X-Carrier-Candidate-Count`, `X-Carrier-Deadline` und `X-Carrier-State`. Die Bridge startet ihre lokale kumulative Uhr unmittelbar vor dem ersten `/session`-Request; der Server friert seine separate absolute Chain-Deadline bei Annahme des ersten automatischen Versuchs ein. Beide verwenden die konfigurierten Offsets und werden bei Ersatzversuchen nicht zurückgesetzt. Für einen bis vier effektive Kandidaten lauten die Attempt-Checkpoints entsprechend `[d3]`, `[d0, d3]`, `[d0, d1, d3]` und `[d0, d1, d2, d3]`; der letzte Kandidat verwendet immer `d3`. Ein Nachfolger bleibt bis zu seinem eigenen Checkpoint zulässig. Die Zustände sind `provisional`, `committed` und `healthy`.
Versuche laufen streng sequenziell. Akzeptierter `OPEN`- oder `DATA`-Fortschritt schreibt den gewählten Carrier sofort fest und schließt die Ersatzgrenze endgültig. Ein authentifiziertes `409` für eine festgeschriebene Kette wiederholt deren Metadaten und ist terminal; es erlaubt keinen weiteren Versuch. Das exakte Replay von `/session` wird nur verwendet, solange dessen Ergebnis mehrdeutig ist. Nach der authentifizierten Auswahl eines provisional Carriers fordert ein Transportfehler direkt den nächsten Versuch an; wurde der vorherige Probe doch committed, antwortet der Server terminal mit `409`, statt einen unsicheren Ersatz zuzulassen. Die endgültige absolute Server-Deadline begrenzt auch einen Nachfolger, dessen Response den Client nie erreicht hat. Dynamisches Umschalten nach dem Commit wird absichtlich nicht unterstützt; dafür ist eine neue Sitzung erforderlich. Die Bridge sendet additive v1-Statusobjekte mit `state`, `phase`, `reason` und `deadline_ms`. `phase=provisional` folgt auf das authentifizierte `WELCOME`; `state=connected,phase=committed` wird erst gesendet, nachdem der ausgewählte Transport echten `OPEN`- oder `DATA`-Fortschritt bestätigt hat. Der Initialisierungsport besitzt eine eigene Pre-`HELLO`-Deadline `bridge_request_secs`, und eine Seitennavigation ist für diese Dokumentinstanz terminal. Eine spätere Initialisierungsnachricht kann eine geschlossene oder im BFCache gehaltene Bridge nicht wiederbeleben.
Jede HTTP-Operation der Bridge besitzt ein absolutes Budget `bridge_retry_secs` und höchstens neun Versuche. `bridge_request_secs` umfasst sowohl den Fetch-Response-Head als auch das vollständige Lesen des Response-Bodys; ein Downlink-Versuch erhält zusätzlich das konfigurierte Long-Poll-Intervall. Netzwerkfehler und Antworten mit `408`, `429`, `502`, `503` oder `504` verwenden begrenzten exponentiellen Backoff, während `Retry-After` das absolute Budget nicht verlängern kann. `carrier_probe_coalesce_ms = 0` sendet den ersten geordneten `OPEN`-Probe sofort. Ein Wert bis 10 ms kann passendes `DATA` aus diesem Fenster aufnehmen; multiplexierte Carrier bewahren die vollständige vorhergehende Frame-Reihenfolge, Lane-Carrier beanspruchen nur die ausgewählte Lane. Vor der Probe-Bestätigung startet kein HTTP-Downlink. Ein multiplexierter WebSocket-Upgrade kann unmittelbar nach seiner Auswahl durch `/session` beginnen und danach eingereihte Probe-Daten aufnehmen; ein Lane-WebSocket wartet auf die bekannte Stream-ID. Versuche laufen streng sequenziell. Akzeptierter `OPEN`- oder `DATA`-Fortschritt schreibt den gewählten Carrier sofort fest und schließt die Ersatzgrenze endgültig. Ein authentifiziertes `409` für eine festgeschriebene Kette wiederholt deren Metadaten und ist terminal; es erlaubt keinen weiteren Versuch. Das exakte Replay von `/session` wird nur verwendet, solange dessen Ergebnis mehrdeutig ist. Nach der authentifizierten Auswahl eines provisional Carriers fordert ein Transportfehler direkt den nächsten Versuch an; wurde der vorherige Probe doch committed, antwortet der Server terminal mit `409`, statt einen unsicheren Ersatz zuzulassen. Die endgültige absolute Server-Deadline begrenzt auch einen Nachfolger, dessen Response den Client nie erreicht hat. Ein In-place-Wechsel nach dem Commit bleibt nicht unterstützt; eine überlebende Bridge stellt sich durch eine frische Serversitzung wieder her.
Nach dem Commit wiederholt ein HTTP-Fehler zunächst den exakten unveränderlichen Request mit dem aktuellen Bearer. Ein erfolgreicher Replay behält die aktuelle Sitzung. WebSocket-Verlust oder ein Foreground-, Online- oder natives Ereignis nach mindestens `reconnect_grace_secs` Scheduler-Lücke startet eine Recovery-Epoche. Die Bridge sendet genau einen GET an ihren ursprünglichen konfigurierten Basis-Root mit `bridge=<capability>`, `Accept: application/vnd.telemt.web-recovery+json` und optionaler aktueller Bearer-Authorization. Eine positive Antwort ist ein nicht cachebares JSON-Dokument mit höchstens 1024 Bytes, einem frischen Bootstrap und den aktuellen Limits, Timeouts sowie der Negotiation-Richtlinie. Telemt gibt diesen Bootstrap aus, bevor eine passende aktuelle Sitzung synchron beendet wird, sodass eine Neuerstellung auch bei Kapazität für nur eine Sitzung möglich bleibt. Unbekannte oder bereits beendete Bearer erhalten dieselbe positive Repräsentation; fehlerhafte Recovery-Header sowie deaktivierte Admission, Pause, Drain und Kapazitätsablehnung folgen dem bereinigten Decoy-Pfad.
Die Recovery-Epoche besitzt eine gemeinsame absolute Wall-/Monotonic-Deadline `bridge_recovery_secs`, genau einen Request für das Recovery-Dokument und begrenzte Carrier-Wiederholungen mit Backoff von 250 ms bis 2 s. Während der Recovery wird der Status höchstens alle 2,5 Sekunden wiederholt. Eine frische Inkarnation bricht alte Requests, Sockets, Lanes und Queues ab und gibt sie frei, sendet für jeden noch aktiven nativen Stream genau ein synthetisches `CLOSE`, unterdrückt ein zweites `WELCOME` und committed erst nach echtem Carrier-Fortschritt. Beendete Stream-IDs bleiben in einer begrenzten Menge, damit gültige verspätete Frames nicht in einen neuen Stream gelangen; die native Seite muss eine neue Stream-ID vergeben. Häufige native Reconnect-Versuche sind zulässig, verlängern aber weder die Recovery-Epoche noch halten sie alten Inkarnationszustand. Das Zerstören der WebView zerstört auch diesen Recovery-Owner; ein nativer Supervisor muss danach ein neues Bridge-Dokument erzeugen.
Jede reguläre HTTP-Carrier-Operation der Bridge besitzt ein absolutes Budget `bridge_retry_secs` und höchstens neun Versuche. `bridge_request_secs` umfasst sowohl den Fetch-Response-Head als auch das vollständige Lesen des Response-Bodys; ein Downlink-Versuch erhält zusätzlich das konfigurierte Long-Poll-Intervall. Netzwerkfehler und Antworten mit `408`, `429`, `502`, `503` oder `504` verwenden begrenzten exponentiellen Backoff, während `Retry-After` das absolute Budget nicht verlängern kann. `carrier_probe_coalesce_ms = 0` sendet den ersten geordneten `OPEN`-Probe sofort. Ein Wert bis 10 ms kann passendes `DATA` aus diesem Fenster aufnehmen; multiplexierte Carrier bewahren die vollständige vorhergehende Frame-Reihenfolge, Lane-Carrier beanspruchen nur die ausgewählte Lane. Vor der Probe-Bestätigung startet kein HTTP-Downlink. Ein multiplexierter WebSocket-Upgrade kann unmittelbar nach seiner Auswahl durch `/session` beginnen und danach eingereihte Probe-Daten aufnehmen; ein Lane-WebSocket wartet auf die bekannte Stream-ID.
Response-Bodys werden innerhalb expliziter Endpunktgrenzen gestreamt: `/session` enthält exakt acht Bytes, ein erfolgreicher `/down` höchstens `carrier_batch_bytes`, und bodylose Antworten akzeptieren null Bytes. Deklarierter Überlauf wird vor dem Lesen abgelehnt; ein Überlauf beim Streaming oder zu viele Chunks bricht den Reader ab, und Bodys wiederholbarer Antworten werden vor dem Backoff verworfen. Die terminale Bridge-Bereinigung sendet höchstens ein authentifiziertes `DELETE`. Kanonische Transportfehler werden für Diagnosen nach `X-Carrier-Failure` kopiert, Navigation und explizites Schließen bleiben nicht lernende Gründe.
Automatische WebSockets verwenden `tproxy-auto-v1.<session-token>` beziehungsweise `tproxy-auto-lane-v1.<session-token>.<stream-id>`. Die erste akzeptierte Binärnachricht mit echtem `OPEN`- oder `DATA`-Fortschritt schreibt den Carrier fest; danach schreibt der Server eine leere binäre Commit-Bestätigung auf genau diese Verbindung. Ping/Pong schreibt keinen Carrier fest und zählt nicht als Learning-Evidenz. Automatische WebSockets verwenden `tproxy-auto-v1.<session-token>` beziehungsweise `tproxy-auto-lane-v1.<session-token>.<stream-id>`. Die erste akzeptierte Binärnachricht mit echtem `OPEN`- oder `DATA`-Fortschritt schreibt den Carrier fest; danach schreibt der Server eine leere binäre Commit-Bestätigung auf genau diese Verbindung. Ping/Pong schreibt keinen Carrier fest und zählt nicht als Learning-Evidenz.
Ein festgeschriebener Versuch wird erst healthy, wenn transportspezifische bidirektionale Evidenz für `carrier_health_secs` gültig bleibt. HTTPS erfordert akzeptiertes `DATA`, einen bestätigten nicht leeren Post-Commit-Downlink-Batch sowie authentifizierte Aktivität an oder nach der Health-Deadline. WebSocket erfordert die geschriebene exakte Commit-Bestätigung, danach akzeptiertes `OPEN` oder `DATA` desselben Owners und einen bis zum Ende des Intervalls lebenden Owner. Ein früheres Schließen ist neutral und erzeugt kein Lernergebnis. Ein festgeschriebener Versuch wird erst healthy, wenn transportspezifische bidirektionale Evidenz für `carrier_health_secs` gültig bleibt. HTTPS erfordert akzeptiertes `DATA`, einen bestätigten nicht leeren Post-Commit-Downlink-Batch sowie authentifizierte Aktivität an oder nach der Health-Deadline. WebSocket erfordert die geschriebene exakte Commit-Bestätigung, danach akzeptiertes `OPEN` oder `DATA` desselben Owners und einen bis zum Ende des Intervalls lebenden Owner. Health-Veröffentlichung, Owner-Eviction und Close besitzen genau einen terminalen Gewinner. Ein früheres Schließen bleibt für Ranking-Evidenz neutral, ist aber als Diagnoseergebnis `closed_before_health` sichtbar.
Das Lernen ist prozesslokal, speicherresident, ausschließlich positiv und durch `max_carrier_learning_entries` begrenzt. Es sortiert nur vom Client unterstützte konfigurierte Kandidaten, hält den konfigurierten Fallback stets zuletzt und bewahrt bei gleichen Scores die Konfigurationsreihenfolge. User-Agent- und Profilevidenz haben Primärgewicht; eine zulässige IP dient nur als Tie-Breaker. IP-Evidenz erfordert genau eine explizite, global routbare `X-Forwarded-For`-Adresse; private, Loopback-, Link-Local-, Carrier-Grade-NAT-, Dokumentations-, Multicast- und entsprechende IPv4-Mapped-Adressen sind ausgeschlossen. Vom Client gemeldete Fehlerkategorien und Request-Latenz sind ausschließlich diagnostisch und erzeugen weder negative noch Ranking-Evidenz. `conservative` erfordert 3 User-Agent-Ergebnisse oder 8 Profilergebnisse aus 4 Kohorten und deaktiviert IP-Evidenz; `balanced` verwendet 2, 6 aus 3 Kohorten und 3 zulässige IP-Ergebnisse; `aggressive` verwendet 1, 4 aus 2 Kohorten und 1 IP-Ergebnis. Deaktiviertes Lernen oder eine geänderte Richtlinie verwirft beim Reload inkompatible Evidenz, ohne laufende Sitzungen zu verändern. Das Lernen ist prozesslokal, speicherresident, ausschließlich positiv und durch `max_carrier_learning_entries` begrenzt. Es sortiert nur vom Client unterstützte konfigurierte Kandidaten, hält den konfigurierten Fallback stets zuletzt und bewahrt bei gleichen Scores die Konfigurationsreihenfolge. User-Agent- und Profilevidenz haben Primärgewicht; eine zulässige IP dient nur als Tie-Breaker. IP-Evidenz erfordert genau eine explizite, global routbare `X-Forwarded-For`-Adresse; private, Loopback-, Link-Local-, Carrier-Grade-NAT-, Dokumentations-, Multicast- und entsprechende IPv4-Mapped-Adressen sind ausgeschlossen. Vom Client gemeldete Fehlerkategorien und Request-Latenz sind ausschließlich diagnostisch und erzeugen weder negative noch Ranking-Evidenz. `conservative` erfordert 3 User-Agent-Ergebnisse oder 8 Profilergebnisse aus 4 Kohorten und deaktiviert IP-Evidenz; `balanced` verwendet 2, 6 aus 3 Kohorten und 3 zulässige IP-Ergebnisse; `aggressive` verwendet 1, 4 aus 2 Kohorten und 1 IP-Ergebnis. Ein Generationswechsel mit identischer Learning-Semantik bewahrt die Evidenz und veröffentlicht deren Generation-Fence atomar neu. Das Deaktivieren des Learnings oder eine Änderung von Aggressiveness, Evidenz-Lebensdauer oder Health-Fenster erhöht die Evidenz-Epoche und trennt inkompatiblen Zustand ab; veraltete Ergebnisse können ihn nicht erneut füllen.
`https` bleibt der Default und behält das ursprüngliche serialisierte Verhalten bei. Bei `https-lanes` ist Lane null für Session-Steuerung reserviert, und jeder logische Stream ungleich null erhält eine eigene Lane. Jede Lane besitzt eigene Uplink-Sequenzen, Retry-Digests, Downlink-Cursor, nicht bestätigte Replay-Batches, Queues und einen Newest-Poll-Wins-Lebenszyklus. Ein langsamer Stream blockiert daher keinen anderen Stream auf der WEB-Protokollebene. `https` bleibt der Default und behält das ursprüngliche serialisierte Verhalten bei. Bei `https-lanes` ist Lane null für Session-Steuerung reserviert, und jeder logische Stream ungleich null erhält eine eigene Lane. Jede Lane besitzt eigene Uplink-Sequenzen, Retry-Digests, Downlink-Cursor, nicht bestätigte Replay-Batches, Queues und einen Newest-Poll-Wins-Lebenszyklus. Ein langsamer Stream blockiert daher keinen anderen Stream auf der WEB-Protokollebene.
@@ -132,9 +163,9 @@ Damit entfällt die Serialisierung zwischen WEB-Streams auf Anwendungsebene. Öf
Alle Lane-Queues und residenten Response-Bodys bleiben innerhalb der vorhandenen Byte-/Item-Budgets pro Sitzung und Prozess. Telemt begrenzt jede Lane zusätzlich durch `pending_bytes_per_lane` und `pending_items_per_lane`; die erzeugte Bridge begrenzt ihre entsprechenden Queues auf 8 MiB und 1024 Elemente. Lane-Long-Polls dürfen höchstens die Hälfte von `web.limits.max_http_handlers` belegen, sodass Handler-Kapazität für Sitzungserstellung, Uplink, DELETE und andere Steuerarbeit verbleibt. `https` erfordert `max_http_handlers >= 2`, `https-lanes` erfordert `max_http_handlers >= 4`. Alle Lane-Queues und residenten Response-Bodys bleiben innerhalb der vorhandenen Byte-/Item-Budgets pro Sitzung und Prozess. Telemt begrenzt jede Lane zusätzlich durch `pending_bytes_per_lane` und `pending_items_per_lane`; die erzeugte Bridge begrenzt ihre entsprechenden Queues auf 8 MiB und 1024 Elemente. Lane-Long-Polls dürfen höchstens die Hälfte von `web.limits.max_http_handlers` belegen, sodass Handler-Kapazität für Sitzungserstellung, Uplink, DELETE und andere Steuerarbeit verbleibt. `https` erfordert `max_http_handlers >= 2`, `https-lanes` erfordert `max_http_handlers >= 4`.
Die Pfade `/api/v1/up` und `/api/v1/down` ändern sich nicht. Bei `https-lanes` enthält jeder Request an diese Pfade genau einen kanonischen dezimalen `X-Lane-ID`-Header. Die Uplink-Sequenz beginnt pro Lane unabhängig bei `1`, der Downlink-Cursor bei `0`. Lane null akzeptiert nur Session-`PONG`; jeder Frame einer Lane ungleich null muss dieselbe Stream-ID tragen, und eine neue Lane muss mit `OPEN` beginnen. Ein kanonischer Cursor-null-Downlink, der kurz vor dem `OPEN` seiner Lane eintrifft, wartet bis zu `lane_open_wait_secs`, ohne Lane-Zustand anzulegen; Grenzen pro Sitzung und prozessweite Hilfs-Permits begrenzen diese Wartefälle. Nach Ablauf folgt eine leere `204`-Response, während eine fehlende Lane mit fortgeschrittenem Cursor weiterhin als Protokollfehler über den Decoy-Pfad behandelt wird. Nachdem eingereihte und nicht bestätigte Downlink-Daten einer geschlossenen Lane vollständig abgearbeitet sind, antwortet Telemt leer mit `X-Lane-Closed: 1`, und die Bridge beendet deren Polling. Wiederholungen bleiben byte-identisch und spielen die ursprüngliche Bestätigung oder den Downlink-Batch erneut aus. Die Suffixe `/api/v1/up` und `/api/v1/down` ändern sich nicht und werden an die konfigurierte Basis angehängt. Bei `https-lanes` enthält jeder Request an diese Pfade genau einen kanonischen dezimalen `X-Lane-ID`-Header. Die Uplink-Sequenz beginnt pro Lane unabhängig bei `1`, der Downlink-Cursor bei `0`. Lane null akzeptiert nur Session-`PONG`; jeder Frame einer Lane ungleich null muss dieselbe Stream-ID tragen, und eine neue Lane muss mit `OPEN` beginnen. Ein kanonischer Cursor-null-Downlink, der kurz vor dem `OPEN` seiner Lane eintrifft, wartet bis zu `lane_open_wait_secs`, ohne Lane-Zustand anzulegen; Grenzen pro Sitzung und prozessweite Hilfs-Permits begrenzen diese Wartefälle. Nach Ablauf folgt eine leere `204`-Response, während eine fehlende Lane mit fortgeschrittenem Cursor weiterhin als Protokollfehler über den Decoy-Pfad behandelt wird. Nachdem eingereihte und nicht bestätigte Downlink-Daten einer geschlossenen Lane vollständig abgearbeitet sind, antwortet Telemt leer mit `X-Lane-Closed: 1`, und die Bridge beendet deren Polling. Wiederholungen bleiben byte-identisch und spielen die ursprüngliche Bestätigung oder den Downlink-Batch erneut aus.
Beide WebSocket-Carrier erstellen und löschen die übergeordnete Sitzung weiterhin über HTTPS und verwenden danach einen strikten Upgrade-Request ohne Body an `GET /api/v1/ws`. `websocket` übermittelt in `Sec-WebSocket-Protocol` exakt `tproxy-v1.<session-token>`; binäre Messages sind geordnete Carrier-Batches, und ein Protokoll-, Deadline- oder Verbindungsfehler schließt die gesamte übergeordnete Sitzung. `websocket-lanes` übermittelt exakt `tproxy-lane-v1.<session-token>.<stream-id>`, wobei die Stream-ID kanonisch dezimal im Bereich `1..=16777215` steht. Die erste binäre Message muss mit `OPEN` beginnen, alle Frames müssen diese Stream-ID verwenden und ein Fehler nach dem Upgrade schließt nur diese Lane. Es gibt keinen Lane-null-WebSocket: HTTPS transportiert `HELLO` und `WELCOME`, während RFC-6455-Ping/Pong die Verbindungsliveness gewährleistet. Beide WebSocket-Carrier erstellen und löschen die übergeordnete Sitzung weiterhin über HTTPS und verwenden danach einen strikten Upgrade-GET ohne Body an der konfigurierten Basis plus `/api/v1/ws`. `websocket` übermittelt in `Sec-WebSocket-Protocol` exakt `tproxy-v1.<session-token>`; binäre Messages sind geordnete Carrier-Batches, und ein Protokoll-, Deadline- oder Verbindungsfehler schließt die gesamte übergeordnete Sitzung. `websocket-lanes` übermittelt exakt `tproxy-lane-v1.<session-token>.<stream-id>`, wobei die Stream-ID kanonisch dezimal im Bereich `1..=16777215` steht. Die erste binäre Message muss mit `OPEN` beginnen, alle Frames müssen diese Stream-ID verwenden und ein Fehler nach dem Upgrade schließt nur diese Lane. Es gibt keinen Lane-null-WebSocket: HTTPS transportiert `HELLO` und `WELCOME`, während RFC-6455-Ping/Pong die Verbindungsliveness gewährleistet.
Vor HTTP `101` wird eine WebSocket-Lane-Reservierung an die exakte Prozessverbindung und Lane-Inkarnation gebunden; ein akzeptiertes `OPEN` überträgt die Ownership auf die exakte Stream-Inkarnation, bevor deren Backend-Task laufen kann. Ein verspäteter Poll, Close oder Reservierungs-Drop eines älteren Sockets kann einen Ersatz mit derselben numerischen Lane-ID weder bestätigen noch schließen oder freigeben. Vor HTTP `101` wird eine WebSocket-Lane-Reservierung an die exakte Prozessverbindung und Lane-Inkarnation gebunden; ein akzeptiertes `OPEN` überträgt die Ownership auf die exakte Stream-Inkarnation, bevor deren Backend-Task laufen kann. Ein verspäteter Poll, Close oder Reservierungs-Drop eines älteren Sockets kann einen Ersatz mit derselben numerischen Lane-ID weder bestätigen noch schließen oder freigeben.
@@ -144,7 +175,7 @@ Jeder Authentifizierungs-, Shape-, Lane-Reservierungs- oder Kapazitätsfehler vo
Der WEB-Listener muss `proxy_protocol = false` und `reuse_allow = false` verwenden. `client_mss`, `synlimit`, `announce` und `announce_ip` sind nicht zulässig. `web_trusted_proxy_cidrs` muss nicht leer sein und darf nur die unmittelbar vorgeschalteten NGINX- oder HAProxy-Peers enthalten; `/0`-Netze werden abgelehnt. Der WEB-Listener muss `proxy_protocol = false` und `reuse_allow = false` verwenden. `client_mss`, `synlimit`, `announce` und `announce_ip` sind nicht zulässig. `web_trusted_proxy_cidrs` muss nicht leer sein und darf nur die unmittelbar vorgeschalteten NGINX- oder HAProxy-Peers enthalten; `/0`-Netze werden abgelehnt.
Der HTTP-Decoy-Origin muss eine Loopback-, Link-Local- oder private IP-Adresse als Literal verwenden. Telemt bewahrt bei gewöhnlichen Requests Methode, Pfad, Query, Header, gestreamten Body, Response-Status, Header und Body und entfernt Hop-by-Hop-Header. Vor dem Fallback auf den Decoy entfernt Telemt Carrier-Zugangsdaten und Bodys aus fehlerhaften Carrier-Requests. Der HTTP-Decoy-Origin muss eine Loopback-, Link-Local- oder private IP-Adresse als Literal verwenden. Telemt bewahrt bei gewöhnlichen Requests Methode, Pfad, Query, Header, gestreamten Body, Response-Status, Header und Body und entfernt Hop-by-Hop-Header. Vor dem Fallback auf den Decoy entfernt Telemt Carrier-Zugangsdaten und Bodys aus fehlerhaften Carrier-Requests. Ein literaler Decoy-Endpunkt, der exakt einem effektiven WEB-Listener entspricht oder auf demselben Port von dessen gleichfamiliärer Wildcard-Adresse erfasst wird, wird abgelehnt. Indirekte Schleifen über DNS, NGINX, HAProxy oder eine andere Weiterleitungsschicht sind aus der Telemt-Konfiguration nicht beweisbar und müssen betrieblich ausgeschlossen werden.
Alternativ kann ein unveränderlicher Snapshot einer statischen Site verwendet werden: Alternativ kann ein unveränderlicher Snapshot einer statischen Site verwendet werden:
@@ -203,8 +234,18 @@ server {
Platzieren Sie `map` im NGINX-Kontext `http`. `client_max_body_size` muss mindestens `web.limits.max_body_bytes` entsprechen. Read-, Send- und Client-Timeouts müssen sowohl den standardmäßigen 25-Sekunden-Long-Poll als auch das doppelte WebSocket-Liveness-Intervall überschreiten; 65 Sekunden decken die Defaults ab. Überschreiben Sie `X-Forwarded-For`, statt einen Wert anzuhängen. Telemt akzeptiert eine syntaktisch gültige IP-Adresse; fehlt der Header bei einem vertrauenswürdigen TLS-Terminator, verwendet Telemt die Adresse des direkten Peers, doch clientbezogene Limits und Quellrichtlinien sehen dann den Terminator statt des echten Clients. Aktivieren Sie keine Upstream-Wiederholungen: Die Bridge führt byte-identische HTTPS-Wiederholungen aus, ein etablierter WebSocket wird jedoch nie transparent wiederholt. Platzieren Sie `map` im NGINX-Kontext `http`. `client_max_body_size` muss mindestens `web.limits.max_body_bytes` entsprechen. Read-, Send- und Client-Timeouts müssen sowohl den standardmäßigen 25-Sekunden-Long-Poll als auch das doppelte WebSocket-Liveness-Intervall überschreiten; 65 Sekunden decken die Defaults ab. Überschreiben Sie `X-Forwarded-For`, statt einen Wert anzuhängen. Telemt akzeptiert eine syntaktisch gültige IP-Adresse; fehlt der Header bei einem vertrauenswürdigen TLS-Terminator, verwendet Telemt die Adresse des direkten Peers, doch clientbezogene Limits und Quellrichtlinien sehen dann den Terminator statt des echten Clients. Aktivieren Sie keine Upstream-Wiederholungen: Die Bridge führt byte-identische HTTPS-Wiederholungen aus, ein etablierter WebSocket wird jedoch nie transparent wiederholt.
Ersetzen Sie für Prefix-only-Cohosting mit `base_path = "telegram/web"` die Zeile `location /` durch `location ^~ /telegram/web/`. Behalten Sie `proxy_pass http://telemt_web;` ohne URI-Komponente bei und fügen Sie kein `rewrite` hinzu; NGINX muss das ursprüngliche Präfix weitergeben. Requests außerhalb dieses Teilbaums dürfen eine andere Site verwenden, jeder Request innerhalb davon muss jedoch zu Telemt gehen. Definieren Sie außerdem ein exaktes `location = /telegram/web`, das das gewöhnliche Non-WEB-Verhalten der Site verwendet oder unverändert an Telemt und dessen Decoy-Pfad weiterleitet. Andernfalls kann NGINX für den Alias ohne Schrägstrich selbstständig ein slash-ergänzendes `301` erzeugen; dies gehört nicht zum WEB-Vertrag.
Öffentliches HTTP/2 ist für `https-lanes` obligatorisch; verwenden Sie die entsprechende HTTP/2-Direktive der installierten NGINX-Version. WebSocket-Upgrade erfordert HTTP/1.1, daher muss der öffentliche Endpunkt auch HTTP/1.1 zulassen und der private Hop von NGINX zu Telemt bleibt HTTP/1.1. Bewahren Sie `Connection`, `Upgrade` und `Sec-WebSocket-*` wie gezeigt unverändert. Die Upstream-Verbindungskapazität muss die erwarteten gleichzeitigen Lane-Polls oder WebSocket-Lanes tragen; `keepalive` steuert den Idle-Pool und ist keine Nebenläufigkeitsgrenze. Öffentliches HTTP/2 ist für `https-lanes` obligatorisch; verwenden Sie die entsprechende HTTP/2-Direktive der installierten NGINX-Version. WebSocket-Upgrade erfordert HTTP/1.1, daher muss der öffentliche Endpunkt auch HTTP/1.1 zulassen und der private Hop von NGINX zu Telemt bleibt HTTP/1.1. Bewahren Sie `Connection`, `Upgrade` und `Sec-WebSocket-*` wie gezeigt unverändert. Die Upstream-Verbindungskapazität muss die erwarteten gleichzeitigen Lane-Polls oder WebSocket-Lanes tragen; `keepalive` steuert den Idle-Pool und ist keine Nebenläufigkeitsgrenze.
### Verbindungsablehnung und WEB-Kapazität unterscheiden
`connect() failed (111: Connection refused) while connecting to upstream` ist ein TCP-Verbindungsfehler, bevor Telemt einen Socket annimmt. Prüfen Sie, ob der Telemt-Prozess läuft, effektive WEB-Listener-Adresse und -Port mit dem NGINX-Upstream übereinstimmen, beide Prozesse denselben erwarteten Network Namespace und dieselbe Adressfamilie verwenden und keine lokale Firewall die Verbindung aktiv ablehnt. Ein Bind-Fehler beim Start, terminales Entfernen des Listeners oder das Umschalten von NGINX auf einen gewünschten Port, bevor eine neustartpflichtige Listener-Änderung effektiv wird, kann dieses Symptom erzeugen. Druck auf den Kernel-Listen-Backlog ist davon getrennt und erfordert üblicherweise Host-Telemetrie für `ListenOverflows` und `ListenDrops`.
WEB-Kapazität wird erst nach erfolgreichem `accept(2)` durchgesetzt. Erschöpftes `max_http_connections` erzeugt daher das konfigurierte Ergebnis `drop`, `wait` oder `respond`, aber keine Upstream-Verbindungsablehnung. Handler-, Body-, Lane-, Stream-, Queue- und WebSocket-Limits besitzen eigene HTTP-, Decoy- oder streamlokale Fehlergrenzen. Operator-Pause und -Drain lassen den WEB-Listener ebenfalls gebunden und können allein keine Ablehnung erzeugen.
Verwenden Sie `GET /v1/runtime/web/status`, um ausschließlich Telemt-eigenen Zustand zu korrelieren. `ingress.accepting_connections` erfordert eine laufende Veröffentlichung, eine lesbare Runtime und einen aktiven Acceptor für jeden effektiven WEB-Listener. `capacity.saturated_resources`, typisierte Rejection-Summen und Overload-Ergebnisse identifizieren Fehler nach dem Accept. `decoy_upstream` beschreibt nur Telemt's ausgehenden Plain-HTTP-Hop zum Decoy. Keines dieser Felder behauptet, dass der öffentliche NGINX-TLS-Endpunkt erreichbar ist; prüfen Sie diese Grenze mit einem externen TCP/TLS-Probe und NGINX- oder HAProxy-Telemetrie.
## TLS-Terminierung mit HAProxy ## TLS-Terminierung mit HAProxy
```haproxy ```haproxy
@@ -227,7 +268,7 @@ backend telemt_web
server telemt_web_1 127.0.0.1:18080 check server telemt_web_1 127.0.0.1:18080 check
``` ```
Im Frontend oder im Abschnitt `defaults` muss für das standardmäßige WebSocket-Liveness-Intervall auch `timeout client 65s` oder länger gesetzt sein. Für `https-lanes` muss das öffentliche HAProxy-ALPN `h2`, für WebSocket-Upgrade außerdem `http/1.1` enthalten. Bewahren Sie `Connection`, `Upgrade` und `Sec-WebSocket-*` unverändert; Pfad, Raw Query, Body sowie die Carrier-Header `Authorization`, `Content-Type`, `X-Up-Seq`, `X-Down-Cursor` und `X-Lane-ID` dürfen nicht umgeschrieben werden. Im Frontend oder im Abschnitt `defaults` muss für das standardmäßige WebSocket-Liveness-Intervall auch `timeout client 65s` oder länger gesetzt sein. Für `https-lanes` muss das öffentliche HAProxy-ALPN `h2`, für WebSocket-Upgrade außerdem `http/1.1` enthalten. Bewahren Sie `Connection`, `Upgrade` und `Sec-WebSocket-*` unverändert; Pfad, Raw Query, Body sowie die Carrier-Header `Authorization`, `Content-Type`, `X-Up-Seq`, `X-Down-Cursor` und `X-Lane-ID` dürfen nicht umgeschrieben werden. Fügen Sie für Prefix-only-Cohosting `acl telemt_web_path path_beg /telegram/web/` hinzu und verlangen Sie in `use_backend` sowohl Host- als auch Pfad-ACL; entfernen Sie das Präfix nicht.
## Lebenszyklus und Reload-Verhalten ## Lebenszyklus und Reload-Verhalten
@@ -236,7 +277,9 @@ Im Frontend oder im Abschnitt `defaults` muss für das standardmäßige WebSocke
| Bestand der WEB-Listener, Bind-Adresse und Vertrauensrichtlinie | Prozesseigen; Telemt neu starten. | | Bestand der WEB-Listener, Bind-Adresse und Vertrauensrichtlinie | Prozesseigen; Telemt neu starten. |
| Jeder Wert in `[web.limits]` | Prozesseigener Speicher- und Ressourcenvertrag; Telemt neu starten. | | Jeder Wert in `[web.limits]` | Prozesseigener Speicher- und Ressourcenvertrag; Telemt neu starten. |
| `web.enabled`, Carrier-/Negotiation-Richtlinie, `web.debug`, Timeouts, vhosts, Profile und Decoys | Werden vom Config-Watcher oder durch einen Runtime-Generations-Reload angewendet. | | `web.enabled`, Carrier-/Negotiation-Richtlinie, `web.debug`, Timeouts, vhosts, Profile und Decoys | Werden vom Config-Watcher oder durch einen Runtime-Generations-Reload angewendet. |
| Bestehende HTTP-Verbindungen und WEB-Sitzungen | Behalten HTTP-Idle-Grenze, Carrier-Kandidaten, Grenzen, Body-Timeout, Lebensdauer des Replay-Markers geschlossener Token sowie absolute Session-/Negotiation-Deadlines ihres Erstellungszeitpunkts; jede ausgegebene Bridge enthält ihre Request-, Retry- und Probe-Coalescing-Werte. WebSocket-Upgrade-, Open-, Write-, Backpressure- und Eviction-Vorgänge verwenden die unveränderlichen Deadlines der Parent-Sitzung. Neue Bridges verwenden die aktive Richtlinie, neue logische Streams die aktive Relay-Generation. | | Änderung des `base_path` eines vhost | Schaltet Routing neuer HTTP-Requests und Capability-Ableitung atomar um. Geben Sie den generierten Link neu aus. Bereits hochgestufte WebSockets und laufende geroutete Austauschvorgänge laufen weiter. Spätere Requests an die alte Basis mit einem prozessauthentischen Bootstrap- oder Session-Token erhalten ein lokales, nicht cachebares `404`; die nun inaktive alte Capability folgt der gewöhnlichen Decoy-Behandlung. Ein bestehender Session-Bearer bleibt nur an der neuen exakten Basis verwendbar, während ein für die alte Capability ausgegebener ungenutzter Bootstrap an der neuen Basis keine Sitzung erstellen kann. |
| Operator-Pause/-Drain-Zustand | Prozesseigen und flüchtig; übersteht einen Generations-Reload, schreibt niemals Konfiguration und wird nach einem Prozessneustart auf `running` zurückgesetzt. |
| Bestehende HTTP-Verbindungen und WEB-Sitzungen | Behalten HTTP-Idle-Grenze, Carrier-Kandidaten, Grenzen, Body-Timeout, Lebensdauer des Replay-Markers geschlossener Token sowie absolute Session-/Negotiation-Deadlines ihres Erstellungszeitpunkts; jede ausgegebene Bridge enthält ihre Request-, Retry-, Recovery- und Probe-Coalescing-Werte. Eine Recovery-Epoche fixiert ihr aktuelles Bridge-Budget; eine erfolgreiche Recovery-Repräsentation aktualisiert die Richtlinie für spätere Epochen und die frische Sitzung. WebSocket-Upgrade-, Open-, Write-, Backpressure- und Eviction-Vorgänge verwenden die unveränderlichen Deadlines der Parent-Sitzung. Neue Bridges verwenden die aktive Richtlinie, neue logische Streams die aktive Relay-Generation. |
| Beenden des Prozesses | Erfasst den zuletzt geladenen Wert von `web.timeouts.shutdown_secs` einmalig und verwendet dieselbe absolute Deadline für Listener-Acceptoren und Verbindungen sowie WEB-Sitzungen und Hilfstasks. Aufeinanderfolgende Komponenten erhalten keine separaten vollständigen Budgets. | | Beenden des Prozesses | Erfasst den zuletzt geladenen Wert von `web.timeouts.shutdown_secs` einmalig und verwendet dieselbe absolute Deadline für Listener-Acceptoren und Verbindungen sowie WEB-Sitzungen und Hilfstasks. Aufeinanderfolgende Komponenten erhalten keine separaten vollständigen Budgets. |
Jeder logische Stream behält die Client-IP seiner Sitzung und besitzt während der gesamten Relay-Lebensdauer einen prozessweit eindeutigen, von null verschiedenen synthetischen Quellport. Damit bleibt für Direct- und Middle-End-KDF-Routing ein stabiles, kollisionsfreies Quell-/Ziel-Tupel erhalten. Jeder logische Stream behält die Client-IP seiner Sitzung und besitzt während der gesamten Relay-Lebensdauer einen prozessweit eindeutigen, von null verschiedenen synthetischen Quellport. Damit bleibt für Direct- und Middle-End-KDF-Routing ein stabiles, kollisionsfreies Quell-/Ziel-Tupel erhalten.
@@ -245,6 +288,8 @@ Die HTTP-Idle-Erfassung schützt nur explizit begrenzte Request-Body-, Long-Poll
Ein `OPEN` reserviert die begrenzte Eigentümerschaft für logischen Stream und Tupel, verbraucht jedoch noch kein `max_connections`-Permit der Relay-Generation. Telemt erwirbt dieses Permit erst nach dem ersten inneren Byte; die unveränderliche First-Byte-Deadline und Stream-Grenzen begrenzen stille Opens, und erschöpfte Kapazität schließt anschließend nur den betroffenen Stream. Ein `OPEN` reserviert die begrenzte Eigentümerschaft für logischen Stream und Tupel, verbraucht jedoch noch kein `max_connections`-Permit der Relay-Generation. Telemt erwirbt dieses Permit erst nach dem ersten inneren Byte; die unveränderliche First-Byte-Deadline und Stream-Grenzen begrenzen stille Opens, und erschöpfte Kapazität schließt anschließend nur den betroffenen Stream.
Behandeln Sie eine Änderung von `base_path` im laufenden Betrieb als Migration einer Route mit Zugangsdaten. Geben Sie keine neuen alten Links mehr aus, bereiten Sie den neuen Link vor, drainen Sie betroffene Sitzungen soweit möglich, wenden Sie den Reload an, prüfen Sie die neue Route über den öffentlichen TLS-Endpunkt und verteilen Sie erst danach den neuen Link. Leiten Sie sowohl den alten als auch den neuen Frontend-Präfix weiterhin an Telemt, solange alte Capabilities oder Tokens eintreffen können; Telemt muss die zugangsdatenbewusste lokale Ablehnung durchführen. Ein vhost kann nicht gleichzeitig beide Basen akzeptieren. Ein echtes Überlappungsfenster erfordert einen zweiten Hostnamen/vhost und, falls derselbe Host erhalten bleiben muss, eine separate Prozess- oder Deployment-Grenze.
## Verwaltung über die API ## Verwaltung über die API
WEB-Konfiguration, Runtime-Status und begrenzte Runtime-Steuerung verwenden denselben authentifizierten API-Listener. `/web-status` bleibt eine schreibgeschützte HTML-Diagnose; zustandsverändernde Operationen existieren ausschließlich unter `/v1/runtime/web`. WEB-Konfiguration, Runtime-Status und begrenzte Runtime-Steuerung verwenden denselben authentifizierten API-Listener. `/web-status` bleibt eine schreibgeschützte HTML-Diagnose; zustandsverändernde Operationen existieren ausschließlich unter `/v1/runtime/web`.
@@ -257,6 +302,7 @@ WEB-Konfiguration, Runtime-Status und begrenzte Runtime-Steuerung verwenden dens
| Begrenzte serverseitige WEB-Request- und Lifecycle-Details untersuchen | Ja, über ein authentifiziertes `GET /web-status`. | | Begrenzte serverseitige WEB-Request- und Lifecycle-Details untersuchen | Ja, über ein authentifiziertes `GET /web-status`. |
| Lifecycle, Kapazitätsebenen, Learning-/Debug-Zustand und aktive Sitzungen untersuchen | Ja, über `GET /v1/runtime/web/status` und `/v1/runtime/web/sessions`. | | Lifecycle, Kapazitätsebenen, Learning-/Debug-Zustand und aktive Sitzungen untersuchen | Ja, über `GET /v1/runtime/web/status` und `/v1/runtime/web/sessions`. |
| Ausgewählte aktive WEB-Sitzungen schließen | Ja, über die asynchrone Operation `POST /v1/runtime/web/sessions/close`. | | Ausgewählte aktive WEB-Sitzungen schließen | Ja, über die asynchrone Operation `POST /v1/runtime/web/sessions/close`. |
| Neue WEB-Arbeit pausieren, mit Deadline drainen oder fortsetzen | Ja, über `/v1/runtime/web/lifecycle/{pause,drain,resume}`. |
| Debug-Datensätze löschen oder Carrier-Learning zurücksetzen | Ja, über die entsprechenden Runtime-POST-Endpunkte. | | Debug-Datensätze löschen oder Carrier-Learning zurücksetzen | Ja, über die entsprechenden Runtime-POST-Endpunkte. |
| `[access.users]` verwalten | Ja, über `/v1/users`. Das Erstellen eines Benutzers erzeugt kein WEB-Profil. | | `[access.users]` verwalten | Ja, über `/v1/users`. Das Erstellen eines Benutzers erzeugt kein WEB-Profil. |
| Einen Benutzer widerrufen | Ja. `/v1/users/{username}/disable` aktualisiert die Admission sofort und beendet die aktiven Sitzungen dieses Benutzers. | | Einen Benutzer widerrufen | Ja. `/v1/users/{username}/disable` aktualisiert die Admission sofort und beendet die aktiven Sitzungen dieses Benutzers. |
@@ -276,18 +322,25 @@ Die API-Whitelist prüft den direkten TCP-Peer und vertraut `X-Forwarded-For` ni
### Runtime-Status und Steuerung ### Runtime-Status und Steuerung
`GET /v1/runtime/web/status` liefert immer den veröffentlichten Lifecycle (`starting`, `no_web_listener`, `running`, `draining`, `drained` oder `deadline_exceeded`), dessen Epoche und Alter, die effektiven Listener-Adressen und die Verfügbarkeit. Solange die prozesseigene WEB-Runtime lebt, ergänzt `runtime` die zufällige 128-Bit-`runtime_instance`, die aktive Generation, unveränderliche Limits, ebenenlokale Kapazitätszähler, Carrier-Learning-/Debug-Epochen und Summen. Die Statuserfassung liest jede Ebene nicht blockierend: Eine umkämpfte Ebene wird ausgelassen und in `partial` benannt; der Endpunkt wartet nie auf die Datenebene, bereinigt sie nicht und verändert sie nicht. `GET /v1/runtime/web/status` liefert immer den veröffentlichten Ingress-Lifecycle (`starting`, `no_web_listener`, `running`, `draining`, `drained` oder `deadline_exceeded`), dessen Epoche und Alter, effektive Listener-Adressen und rückwärtskompatible Runtime-Verfügbarkeit. `ingress` meldet unabhängig konfigurierte Listener, aktive Acceptors, Accepting-Zustand, Accept-Summen und einen stabilen Grund. `capacity` meldet die effektive Policy für angenommene überlastete Sockets, feste Ressourcennutzung, momentane Sättigung, partielle Ebenen, typisierte Rejection-Entscheidungen und Overload-Ergebnisse. `decoy_upstream` meldet feste Ergebnisse und das Alter des letzten internen Origin-Ergebnisses. `decoy_fasttrack` meldet den effektiven, beim Neustart eingefrorenen Modus und die vollständige feste Dispositionsmenge auch bei nicht verfügbarer Runtime-Manager-Ebene. `carrier_negotiation` meldet stets feste Matrizen für Auswahl, vom Client gemeldete Fehler sowie terminale Health-/Learning-Ergebnisse aus Publication-Ownership. Solange die prozesseigene WEB-Runtime lebt, zeigt `operator_lifecycle` unabhängig `running`, `paused`, `draining`, `force_closing` oder `drained`, seine eigene Epoche und Admission-Flags sowie den aktiven oder letzten Drain. `runtime` ergänzt die zufällige 128-Bit-`runtime_instance`, die aktive Generation, unveränderliche Limits, ebenenlokale Kapazitätszähler, Carrier-Learning-/Debug-Epochen und Summen. Die Statuserfassung liest jede Ebene nicht blockierend: Eine umkämpfte Ebene wird ausgelassen und in `partial` benannt; der Endpunkt wartet nie auf die Datenebene, bereinigt sie nicht und verändert sie nicht.
Prometheus exportiert dieselben prozesseigenen Ebenen als `telemt_web_*`-Familien mit fester Kardinalität: One-Hot-Zustände für Ingress und Operator, Listener-/Accept-Zähler, Kapazitätsnutzung und -sättigung, typisierte terminale Ablehnungen, Ergebnisse überlasteter angenommener Sockets, interne Decoy-Origin-Ergebnisse sowie Session-/Stream-/Carrier-Summen. Das Decoy-Routing ergänzt `telemt_web_decoy_fasttrack_mode` als One-Hot-Gauge und `telemt_web_decoy_fasttrack_requests_total{disposition}` mit festen Dispositionen. Carrier-Negotiation verwendet `telemt_web_carrier_selections_total`, `telemt_web_carrier_reported_failures_total`, `telemt_web_carrier_learning_outcomes_total`, One-Hot-Gauges für Learning-Zustand und -Policy sowie Used-/Limit-Gauges für Einträge. Labels sind geschlossene Enums oder feste Ressourcennamen; Benutzer, Host, Client-IP, Token, Profilschlüssel, Runtime-Instanz, Listener-Adresse und Generation-ID werden nie zu Labels. Ein erfolgreicher `wait`-Ausgang erhöht keinen Rejection-Zähler.
`GET /v1/runtime/web/sessions` liefert standardmäßig höchstens 50 und bei gesetztem `limit` höchstens 200 Sitzungen. Der geordnete Scan ist auf 1000 Kandidaten begrenzt. `cursor` und `session_ref` verwenden die undurchsichtige kanonische Form `ws1.<runtime-instance>.<lowercase-hex-id>`; ein exakter `session_ref` darf nicht mit `cursor` oder `limit` kombiniert werden. Filter sind `ip`, `host`, `user`, `user_agent_id`, `key_id`, `carrier` und `state`; doppelte oder unbekannte Query-Felder werden abgelehnt. Der Detailpfad lautet `GET /v1/runtime/web/sessions/{session_ref}`. Ein gespeicherter Tombstone einer geschlossenen Sitzung ergibt `410`; ein umkämpfter exakter Snapshot ergibt `503 web_snapshot_busy`. Antworten enthalten nur begrenzte, nicht geheime Metadaten und niemals Bootstrap-/Session-Bearer, Capabilities, Secret-Hashes oder synthetische KDF-Ports. `GET /v1/runtime/web/sessions` liefert standardmäßig höchstens 50 und bei gesetztem `limit` höchstens 200 Sitzungen. Der geordnete Scan ist auf 1000 Kandidaten begrenzt. `cursor` und `session_ref` verwenden die undurchsichtige kanonische Form `ws1.<runtime-instance>.<lowercase-hex-id>`; ein exakter `session_ref` darf nicht mit `cursor` oder `limit` kombiniert werden. Filter sind `ip`, `host`, `user`, `user_agent_id`, `key_id`, `carrier` und `state`; doppelte oder unbekannte Query-Felder werden abgelehnt. Der Detailpfad lautet `GET /v1/runtime/web/sessions/{session_ref}`. Ein gespeicherter Tombstone einer geschlossenen Sitzung ergibt `410`; ein umkämpfter exakter Snapshot ergibt `503 web_snapshot_busy`. Antworten enthalten nur begrenzte, nicht geheime Metadaten und niemals Bootstrap-/Session-Bearer, Capabilities, Secret-Hashes oder synthetische KDF-Ports.
Jeder Runtime-POST verlangt exakt `Content-Type: application/json`, lehnt unbekannte JSON-Felder ab, beachtet API-Authentifizierung, Whitelist und `read_only` und enthält die aktuelle `runtime_instance` als ABA-Sperre. Verfügbare Steuerungen: Jeder Runtime-POST verlangt exakt `Content-Type: application/json`, lehnt unbekannte JSON-Felder ab, beachtet API-Authentifizierung, Whitelist und `read_only` und enthält die aktuelle `runtime_instance` als ABA-Sperre. Verfügbare Steuerungen:
- `POST /v1/runtime/web/lifecycle/pause` mit `{"runtime_instance":"..."}`. Nach einer linearisierbaren Fence blockiert dies neue Bootstrap-, Session-Inkarnations-, Ersatz- und Logical-Stream-Admission. Bestehende Carrier-Austauschvorgänge und Streams laufen weiter, exaktes Session-Replay bleibt verfügbar und Bridge-Ablehnung bleibt auf dem Decoy-Pfad.
- `POST /v1/runtime/web/lifecycle/drain` mit `{"runtime_instance":"...","timeout_secs":30}`. Die Antwort ist `202`; dieselbe Admission-Fence bleibt geschlossen, während asynchron auf Sitzungen, Streams und sessioneigene WebSockets gewartet wird. An der monotonen Deadline wird Close für alle verbleibenden aktiven Sitzungen signalisiert und bis zur bestätigten Null `force_closing` gemeldet. Natürlicher und erzwungener Abschluss bleiben bis zum Resume geschlossen. Ein zweiter gleichzeitiger Drain ergibt `409 web_lifecycle_in_progress`.
- `POST /v1/runtime/web/lifecycle/resume` mit `{"runtime_instance":"..."}`. Dies bricht einen aktiven Drain ab und öffnet ausschließlich die Operator-Admission. Wenn Forced Close bereits committed wurde, kann die alte Session-Cancellation nicht rückgängig gemacht werden. Config-, User-, Generation- und terminale Shutdown-Gates bleiben vorrangig.
- `POST /v1/runtime/web/sessions/close` mit genau einem Selektor: `{"kind":"refs","session_refs":[...]}`, `{"kind":"filter",...}` oder `{"kind":"all"}`. Exakte Referenzen sind auf 200 begrenzt, ein Filter darf nicht leer sein, nur eine Close-Operation darf laufen, und `all` wird abgelehnt, solange die effektive Ausgabe aktiviert ist. Die `202`-Antwort liefert `operation_id`; fragen Sie `GET /v1/runtime/web/operations/{operation_id}` ab. Die Operation scannt in Blöcken von 128 nur Sitzungen bis einschließlich ihres beim Start fixierten High-Water-Marks. - `POST /v1/runtime/web/sessions/close` mit genau einem Selektor: `{"kind":"refs","session_refs":[...]}`, `{"kind":"filter",...}` oder `{"kind":"all"}`. Exakte Referenzen sind auf 200 begrenzt, ein Filter darf nicht leer sein, nur eine Close-Operation darf laufen, und `all` wird abgelehnt, solange die effektive Ausgabe aktiviert ist. Die `202`-Antwort liefert `operation_id`; fragen Sie `GET /v1/runtime/web/operations/{operation_id}` ab. Die Operation scannt in Blöcken von 128 nur Sitzungen bis einschließlich ihres beim Start fixierten High-Water-Marks.
- `POST /v1/runtime/web/debug/clear` mit `{"runtime_instance":"..."}`. Die Antwort meldet gelöschte Datensätze, weiterhin von bereits gerenderten Snapshots gehaltene Bytes und die neue Epoche. Laufende Writer der alten Epoche können den Ring nicht erneut füllen. - `POST /v1/runtime/web/debug/clear` mit `{"runtime_instance":"..."}`. Die Antwort meldet gelöschte Datensätze, weiterhin von bereits gerenderten Snapshots gehaltene Bytes und die neue Epoche. Laufende Writer der alten Epoche können den Ring nicht erneut füllen.
- `POST /v1/runtime/web/carrier-learning/reset` mit derselben Body-Form. Der Endpunkt löscht gespeicherte prozesslokale Evidenz und erhöht die Learning-Epoche; bereits fixierte Versuchsketten und aktive Sitzungen bleiben unverändert. - `POST /v1/runtime/web/carrier-learning/reset` mit derselben Body-Form. Der Endpunkt löscht gespeicherte prozesslokale Evidenz und erhöht die Learning-Epoche; bereits fixierte Versuchsketten und aktive Sitzungen bleiben unverändert.
Für ein deterministisches Close-all patchen Sie `{"web":{"enabled":false}}` mit aktiviertem Runtime-Reload, warten auf `runtime.manager.issuance_enabled = false`, senden den Selektor `all` mit derselben `runtime_instance` und fragen die Operation bis zu einem Endzustand ab. Das Deaktivieren von WEB stoppt neue Bootstrap-/Session-Ausgabe, schließt bestehende Sitzungen aber niemals implizit. Für ein deterministisches Close-all patchen Sie `{"web":{"enabled":false}}` mit aktiviertem Runtime-Reload, warten auf `runtime.manager.issuance_enabled = false`, senden den Selektor `all` mit derselben `runtime_instance` und fragen die Operation bis zu einem Endzustand ab. Das Deaktivieren von WEB stoppt neue Bootstrap-/Session-Ausgabe, schließt bestehende Sitzungen aber niemals implizit.
Der Operator-Lifecycle gilt nur für WEB und ändert weder globale Readiness und Liveness noch native TCP-/Unix-Listener, TLS-Fronting oder Fallback-Verhalten. Eine vor der Pause reservierte WebSocket-Lane ist bereits zugelassene logische Arbeit: Sie darf den Open-Vorgang abschließen und bleibt im Drain-Accounting enthalten. Lifecycle-Ablehnung verbraucht keine Rate-/Quota-Tokens und fügt dem Hot Path keinen Relay-Lock hinzu.
### Serverseitige WEB-Debug-Ansicht ### Serverseitige WEB-Debug-Ansicht
Aktivieren Sie die begrenzte Erfassung in der zuständigen Konfigurationsdatei: Aktivieren Sie die begrenzte Erfassung in der zuständigen Konfigurationsdatei:
@@ -296,6 +349,7 @@ Aktivieren Sie die begrenzte Erfassung in der zuständigen Konfigurationsdatei:
[web.debug] [web.debug]
enabled = true enabled = true
capture_lifecycle = true capture_lifecycle = true
sideband = true
capture_headers = true capture_headers = true
capture_timings = true capture_timings = true
capture_frames = true capture_frames = true
@@ -306,12 +360,14 @@ default_window_secs = 180
max_window_secs = 3600 max_window_secs = 3600
``` ```
Öffnen Sie `http://127.0.0.1:9091/web-status` mit derselben Whitelist direkter Peers und demselben exakten `Authorization`-Header wie für die API. Ein abschließender Slash wird akzeptiert. Nur `GET` ist zulässig. Die Seite unterstützt die Filter `window_secs`, kanonische `ip`, numerische `session`, `user_agent` ohne Beachtung der Groß-/Kleinschreibung und `key`. Wiederholen Sie `group_by=ip`, `group_by=session`, `group_by=user_agent` oder `group_by=key`, um gruppierte Zusammenfassungen zu erstellen; `limit` ist auf `1..=1000` beschränkt. HTTP-Zeilen lassen sich vom Request bis zur Response zu Methode, Pfad, bereinigten Headern, Body-Metadaten oder -Bytes, Zeitpunkten, Frames und typisierten Lifecycle-Ereignissen einschließlich Carrier-Versuch, Commit, Healthy und gemeldetem Fehler aufklappen. Für WebSocket kommen der bereinigte Handshake `GET` → `101` sowie begrenzte Angaben pro Message zu Richtung, Message-Typ, Payload-/Body-Erfassung, Verarbeitungszeit, Verbindungs-/Lane-ID und geparsten inneren Frames hinzu. Rohe Subprotokolle und Session-Tokens werden nie gespeichert. Öffnen Sie `http://127.0.0.1:9091/web-status` mit derselben Whitelist direkter Peers und demselben exakten `Authorization`-Header wie für die API. Ein abschließender Slash wird akzeptiert. Nur `GET` ist zulässig. Die Seite unterstützt die Filter `window_secs`, kanonische `ip`, numerische `session`, `user_agent` ohne Beachtung der Groß-/Kleinschreibung und `key`. Wiederholen Sie `group_by=ip`, `group_by=session`, `group_by=user_agent` oder `group_by=key`, um gruppierte Zusammenfassungen zu erstellen; `limit` ist auf `1..=1000` beschränkt. HTTP-Zeilen lassen sich vom Request bis zur Response zu Methode, Pfad, bereinigten Headern, Body-Metadaten oder -Bytes, Zeitpunkten, Frames und typisierten Lifecycle-Ereignissen einschließlich Carrier-Versuch, Commit, Healthy, gemeldetem Fehler, exaktem Close-Grund, Peer-Lücke und Übergängen des Vorgängers einer wiederhergestellten Sitzung aufklappen. Für WebSocket kommen der bereinigte Handshake `GET` → `101` sowie begrenzte Angaben pro Message zu Richtung, Message-Typ, Payload-/Body-Erfassung, Verarbeitungszeit, Verbindungs-/Lane-ID und geparsten inneren Frames hinzu. Rohe Subprotokolle und Session-Tokens werden nie gespeichert.
Der prozesseigene Ring übersteht den Austausch einer Runtime-Generation. Änderungen der Erfassungs-Policy löschen inkompatible gespeicherte Datensätze; reine Änderungen des Beobachtungsfensters tun dies nicht. Der Ring ist standardmäßig auf 65536 Datensätze und 64 MiB gespeicherte plus in Verarbeitung befindliche Daten begrenzt, die HTML-Response auf 8 MiB und die Gruppierung auf 1024 Gruppen; gleichzeitig dürfen höchstens zwei Response-Bodys Seiten-Permits halten. Ändern Sie `web.limits.debug_records_capacity` oder `web.limits.debug_bytes_global` nur zusammen mit einem Prozessneustart. Ein hot-reload-fähiger Präfix, der nur in eine gleichzeitig erhöhte neustartpflichtige Kapazität passt, wird bis zu diesem Neustart zurückgestellt. Der prozesseigene Ring übersteht den Austausch einer Runtime-Generation. Änderungen der Erfassungs-Policy löschen inkompatible gespeicherte Datensätze; reine Änderungen des Beobachtungsfensters tun dies nicht. Der Ring ist standardmäßig auf 65536 Datensätze und 64 MiB gespeicherte plus in Verarbeitung befindliche Daten begrenzt, die HTML-Response auf 8 MiB und die Gruppierung auf 1024 Gruppen; gleichzeitig dürfen höchstens zwei Response-Bodys Seiten-Permits halten. Ändern Sie `web.limits.debug_records_capacity` oder `web.limits.debug_bytes_global` nur zusammen mit einem Prozessneustart. Ein hot-reload-fähiger Präfix, der nur in eine gleichzeitig erhöhte neustartpflichtige Kapazität passt, wird bis zu diesem Neustart zurückgestellt.
`body_capture = "off"` lässt Bodys aus, `metadata` speichert Längen und Endzustände, `prefix` die konfigurierten Präfixe und `full` erkannte Carrier-Bodys bis `web.limits.max_body_bytes`. Gewöhnliche Decoy-Bodys bleiben auch in `full` auf `decoy_body_prefix_bytes` begrenzt. Queries und rohe Capabilities werden nie gespeichert; Werte von Credential-Headern werden ausgelassen; bekannte WEB-Capabilities und Bearer-Tokens werden aus erfassten Bodys entfernt; der angezeigte Schlüssel ist ein nicht geheimer, domänengetrennter Fingerprint. Die Zeitmessung endet beim Polling des Hyper-Bodys und behauptet weder einen Kernel-Flush noch eine TCP-Bestätigung. `body_capture = "off"` lässt Bodys aus, `metadata` speichert Längen und Endzustände, `prefix` die konfigurierten Präfixe und `full` erkannte Carrier-Bodys bis `web.limits.max_body_bytes`. Gewöhnliche Decoy-Bodys bleiben auch in `full` auf `decoy_body_prefix_bytes` begrenzt. Queries und rohe Capabilities werden nie gespeichert; Werte von Credential-Headern werden ausgelassen; bekannte WEB-Capabilities und Bearer-Tokens werden aus erfassten Bodys entfernt; der angezeigte Schlüssel ist ein nicht geheimer, domänengetrennter Fingerprint. Die Zeitmessung endet beim Polling des Hyper-Bodys und behauptet weder einen Kernel-Flush noch eine TCP-Bestätigung.
Sideband-Berichte der generierten Bridge sind nur wirksam, wenn `enabled`, `capture_lifecycle` und `sideband` alle `true` sind. Die Policy ist hot-reload-fähig, aber nur neu ausgegebene Bridge-Seiten enthalten den Reporter. Jede Seite kann jedes der acht festen Ereignisse höchstens einmal melden: `runtime_started`, `status_posted`, `hello_received`, `boundary_timeout`, `hello_timeout`, `client_close_before_hello`, `document_unloaded_before_hello` und `runtime_error_before_hello`. Berichte sind exakte kanonische JSON-POSTs an `BASEapi/v1/diagnostic`, verwenden den Bootstrap-Bearer, ohne ihn zu verbrauchen, und nehmen nicht am Carrier-Framing teil. Fehlerhafte oder nicht authentifizierte Berichte folgen dem bereinigten Decoy-Pfad.
Nachdem ein Administrator oder Konfigurationssystem die TOML-Datei atomar aktualisiert hat, setzen Sie `TELEMT_API_AUTH` auf den exakten Wert von `auth_header` und starten Sie einen beobachtbaren Generations-Reload: Nachdem ein Administrator oder Konfigurationssystem die TOML-Datei atomar aktualisiert hat, setzen Sie `TELEMT_API_AUTH` auf den exakten Wert von `auth_header` und starten Sie einen beobachtbaren Generations-Reload:
```bash ```bash
@@ -347,20 +403,21 @@ Der vollständige Vertrag für Requests, Revisionen, Fehler und alle Benutzer-En
- Veröffentlichen Sie den unverschlüsselten HTTP-WEB-Listener niemals in einem nicht vertrauenswürdigen Netz. Erzwingen Sie diese Einschränkung auch bei einer Loopback-Bindung mit Host-Firewall-Regeln. - Veröffentlichen Sie den unverschlüsselten HTTP-WEB-Listener niemals in einem nicht vertrauenswürdigen Netz. Erzwingen Sie diese Einschränkung auch bei einer Loopback-Bindung mit Host-Firewall-Regeln.
- Deaktivieren Sie am TLS-Terminator die Protokollierung von Request-Target und Authorization oder verwenden Sie ein geprüftes, redigiertes Format. Raw Queries enthalten Bridge-Capabilities und `Authorization` enthält Bootstrap- oder Session-Bearer-Zugangsdaten. - Deaktivieren Sie am TLS-Terminator die Protokollierung von Request-Target und Authorization oder verwenden Sie ein geprüftes, redigiertes Format. Raw Queries enthalten Bridge-Capabilities und `Authorization` enthält Bootstrap- oder Session-Bearer-Zugangsdaten.
- Enthalten URI oder Header eine aktive Capability oder einen authentischen, vom aktuellen Prozess ausgegebenen Token, entspricht der Request aber nicht dem Carrier-Vertrag, weist Telemt ihn lokal ab. Solche Zugangsdaten werden nie an den Decoy weitergeleitet. Ein lediglich kanonisch aussehender gefälschter Wert bleibt gewöhnlicher Decoy-Datenverkehr.
- Verwenden Sie pro vhost eine stabile öffentliche Adresse. Wenn DNS mehrere Ingress-Adressen liefert, muss jede Bereitstellung die Adresse ihres externen Pfads verwenden. - Verwenden Sie pro vhost eine stabile öffentliche Adresse. Wenn DNS mehrere Ingress-Adressen liefert, muss jede Bereitstellung die Adresse ihres externen Pfads verwenden.
- Bootstrap- und Session-Register sind prozesslokal. Ein Multi-Prozess- oder Multi-Host-Upstream-Pool benötigt Affinität für den vollständigen vhost: Bridge-GET, Sitzungserstellung, Uplink, Downlink und DELETE. Ein einzelner Telemt-Prozess benötigt keine zusätzliche Affinität. - Bootstrap- und Session-Register sind prozesslokal. Ein Multi-Prozess- oder Multi-Host-Upstream-Pool benötigt Affinität für den vollständigen vhost: initialer und Recovery-Root-GET, Sitzungserstellung, Uplink, Downlink, WebSocket-Upgrade und DELETE. Ein einzelner Telemt-Prozess benötigt keine zusätzliche Affinität.
- Ein ungenutzter Bootstrap übersteht einen Konfigurations-Reload nur, wenn die exakte Profilidentität aktiv bleibt: Host, `public_addr`, Benutzer, Secret-Modus, Carrier-Kandidaten, Negotiation-Deadlines und Capability. Bereits erstellte Sitzungen behalten ihren unveränderlichen Carrier und ihre Profilidentität und bleiben lifecycle-bounded. - Ein ungenutzter Bootstrap übersteht einen Konfigurations-Reload nur, wenn die exakte Profilidentität aktiv bleibt: Host, `public_addr`, Benutzer, Secret-Modus, Carrier-Kandidaten, Negotiation-Deadlines und Capability. Bereits erstellte Sitzungen behalten ihren unveränderlichen Carrier und ihre Profilidentität und bleiben lifecycle-bounded.
- Der Decoy gehört zum Anti-Probing-Vertrag. Prüfen Sie sein gewöhnliches 404-Verhalten und die Antwortzeiten über den öffentlichen TLS-Endpunkt, bevor Sie Links verteilen. - Der Decoy gehört zum Anti-Probing-Vertrag. Prüfen Sie sein gewöhnliches 404-Verhalten und die Antwortzeiten über den öffentlichen TLS-Endpunkt, bevor Sie Links verteilen.
## Erstprüfung ## Erstprüfung
1. Starten Sie das neu erstellte Telemt-Binary mit der WEB-Konfiguration und prüfen Sie, dass der private Listener gebunden ist. 1. Starten Sie das neu erstellte Telemt-Binary mit der WEB-Konfiguration und prüfen Sie, dass der private Listener gebunden ist.
2. Prüfen Sie über den öffentlichen TLS-Endpunkt, dass `GET /`, ein unbekannter Pfad und eine ungültige `bridge`-Query die konfigurierte Decoy-Site zurückgeben. 2. Prüfen Sie über den öffentlichen TLS-Endpunkt, dass ein GET auf dem konfigurierten Basis-Root, der Alias ohne abschließenden Schrägstrich, ein unbekannter Pfad innerhalb dieser Basis und eine ungültige `bridge`-Query die beabsichtigte gewöhnliche Site oder den konfigurierten Decoy ohne synthetisierten Redirect zurückgeben. Prüfen Sie bei Prefix-only-Cohosting außerdem, dass der TLS-Terminator den Basispfad bytegenau beibehält.
3. Prüfen Sie, dass Telemt genau eine syntaktisch gültige `X-Forwarded-For`-Adresse und `Host: proxy.example.com` oder `Host: proxy.example.com:443` erhält. 3. Prüfen Sie, dass Telemt genau eine syntaktisch gültige `X-Forwarded-For`-Adresse und `Host: proxy.example.com` oder `Host: proxy.example.com:443` erhält.
4. Importieren Sie den ausgegebenen `tg://webproxy`-Link in den vorgesehenen Telegram-Desktop-Build und stellen Sie eine Proxy-Verbindung her. 4. Importieren Sie den ausgegebenen `tg://webproxy`-Link in den vorgesehenen Telegram-Desktop-Build und stellen Sie eine Proxy-Verbindung her.
5. Bestätigen Sie für `https-lanes`, dass die öffentliche Verbindung HTTP/2 ausgehandelt hat, und testen Sie mindestens zwei gleichzeitige logische Streams; der private Hop zu Telemt bleibt HTTP/1.1. 5. Bestätigen Sie für `https-lanes`, dass die öffentliche Verbindung HTTP/2 ausgehandelt hat, und testen Sie mindestens zwei gleichzeitige logische Streams; der private Hop zu Telemt bleibt HTTP/1.1.
6. Bestätigen Sie für `websocket` eine `101`-Response, binären Relay-Datenverkehr und RFC-6455-Ping/Pong nach 25 Sekunden. Testen Sie für `websocket-lanes` mindestens zwei gleichzeitige Stream-Sockets und prüfen Sie, dass das Schließen oder Beschädigen einer Lane weder Geschwister noch die übergeordnete Sitzung schließt. 6. Bestätigen Sie für `websocket` eine `101`-Response, binären Relay-Datenverkehr und RFC-6455-Ping/Pong nach 25 Sekunden. Testen Sie für `websocket-lanes` mindestens zwei gleichzeitige Stream-Sockets und prüfen Sie, dass das Schließen oder Beschädigen einer Lane weder Geschwister noch die übergeordnete Sitzung schließt.
7. Testen Sie einen Reconnect und mindestens einen Long Poll über 25 Sekunden, um sicherzustellen, dass Frontend-Timeouts den Carrier nicht abbrechen. 7. Testen Sie einen HTTP-Replay und eine Neuerstellung der Sitzung nach einer Scheduler-Lücke; halten Sie danach einen Long Poll länger als 25 Sekunden offen, um sicherzustellen, dass Frontend-Timeouts den Carrier nicht abbrechen.
8. Prüfen Sie Benutzer- und logische MTProxy-Verbindungslimits anhand der Logical-Stream-Zähler und nicht anhand der Zahl der HTTP-Verbindungen. 8. Prüfen Sie Benutzer- und logische MTProxy-Verbindungslimits anhand der Logical-Stream-Zähler und nicht anhand der Zahl der HTTP-Verbindungen.
9. Prüfen Sie bei aktivierter Auto-Negotiation die konfigurierte Reihenfolge, das Replay exakt desselben Versuchs nach einer absichtlich verlorenen Response, das terminale Verhalten nach dem Commit sowie die Lifecycle-Zeilen `carrier_committed` und `carrier_healthy` in `/web-status`. Prüfen Sie, dass ein nativer Client ohne Metadaten den festen `carrier` ohne automatische Response-Header verwendet und explizite Capabilities unverändert bleiben. 9. Prüfen Sie bei aktivierter Auto-Negotiation die konfigurierte Reihenfolge, das Replay exakt desselben Versuchs nach einer absichtlich verlorenen Response, das terminale Verhalten nach dem Commit sowie die Lifecycle-Zeilen `carrier_committed` und `carrier_healthy` in `/web-status`. Prüfen Sie, dass ein nativer Client ohne Metadaten den festen `carrier` ohne automatische Response-Header verwendet und explizite Capabilities unverändert bleiben.
@@ -370,10 +427,12 @@ Der vollständige Vertrag für Requests, Revisionen, Fehler und alle Benutzer-En
| --- | --- | | --- | --- |
| WEB-Konfiguration ist auf dem Datenträger gültig, aber das Listener-Verhalten hat sich nicht geändert | Prüfen Sie `deferred_process_fields`; Listener- und `[web.limits]`-Änderungen erfordern einen Neustart. | | WEB-Konfiguration ist auf dem Datenträger gültig, aber das Listener-Verhalten hat sich nicht geändert | Prüfen Sie `deferred_process_fields`; Listener- und `[web.limits]`-Änderungen erfordern einen Neustart. |
| Carrier-Requests erreichen den Decoy | Prüfen Sie den exakten vhost, den Secret-Modus des Links, das CIDR des direkten Proxys und genau einen syntaktisch gültigen `X-Forwarded-For`-Wert. | | Carrier-Requests erreichen den Decoy | Prüfen Sie den exakten vhost, den Secret-Modus des Links, das CIDR des direkten Proxys und genau einen syntaktisch gültigen `X-Forwarded-For`-Wert. |
| Ein Link funktioniert nach einer Änderung von `base_path` nicht mehr | Importieren Sie den neu ausgegebenen Pfad-Link und prüfen Sie, dass das vollständige neue Präfix Telemt unverändert erreicht. Bestehende Sitzungen können sich nur über die neue exakte Basis wiederherstellen; alte Capabilities sind nicht wiederverwendbar. |
| `/telegram/web` leitet auf `/telegram/web/` um | Fügen Sie für den Pfad ohne Schrägstrich einen exakten Non-WEB-Handler hinzu. Nur der mit Schrägstrich abgeschlossene konfigurierte Teilbaum gehört zum WEB-Vertrag von Telemt. |
| Ein konkurrierender `https-lanes`-Downlink erreicht den Decoy mit `404` | Prüfen Sie, dass er mit `X-Down-Cursor: 0` beginnt, bewahren Sie `X-Lane-ID` und setzen Sie `lane_open_wait_secs` über den beobachteten Abstand zwischen Downlink und `OPEN`. Fortgeschrittene Cursor fehlender Lanes schlagen absichtlich fail-closed fehl. | | Ein konkurrierender `https-lanes`-Downlink erreicht den Decoy mit `404` | Prüfen Sie, dass er mit `X-Down-Cursor: 0` beginnt, bewahren Sie `X-Lane-ID` und setzen Sie `lane_open_wait_secs` über den beobachteten Abstand zwischen Downlink und `OPEN`. Fortgeschrittene Cursor fehlender Lanes schlagen absichtlich fail-closed fehl. |
| Auto-Negotiation wechselt weiter, nachdem Daten bereits akzeptiert wurden | Das ist ungültig. Prüfen Sie das authentifizierte `X-Carrier-State`-Replay und das Carrier-Commit-Lifecycle-Ereignis; `committed` oder `healthy` ist terminal und erfordert eine neue Sitzung. | | Auto-Negotiation wechselt weiter, nachdem Daten bereits akzeptiert wurden | Das ist ungültig. Prüfen Sie das authentifizierte `X-Carrier-State`-Replay und das Carrier-Commit-Lifecycle-Ereignis; `committed` oder `healthy` ist terminal und erfordert eine neue Sitzung. |
| Long Polls werden nach einem festen Intervall getrennt | Setzen Sie Client-, Server-, Sende- und Lese-Timeouts von NGINX/HAProxy über `web.timeouts.long_poll_secs`. | | Long Polls werden nach einem festen Intervall getrennt | Setzen Sie Client-, Server-, Sende- und Lese-Timeouts von NGINX/HAProxy über `web.timeouts.long_poll_secs`. |
| WebSocket-Upgrade erreicht statt `101` den Decoy | Bewahren Sie HTTP/1.1 `Connection: Upgrade`, `Upgrade: websocket`, das einzelne exakte `Sec-WebSocket-Protocol` und den kanonischen bodylosen Request `/api/v1/ws`. Prüfen Sie außerdem Carrier-/Session-Kompatibilität und die Prozess-Verbindungsreserve. | | WebSocket-Upgrade erreicht statt `101` den Decoy | Bewahren Sie HTTP/1.1 `Connection: Upgrade`, `Upgrade: websocket`, das einzelne exakte `Sec-WebSocket-Protocol` und den kanonischen bodylosen Request an der konfigurierten Basis plus `/api/v1/ws`. Prüfen Sie außerdem Carrier-/Session-Kompatibilität und die Prozess-Verbindungsreserve. |
| Ein `websocket-lanes`-Stream wurde geschlossen, Geschwister bleiben aber verbunden | Dies ist die beabsichtigte Fehlergrenze. Prüfen Sie die Message-/Frame-Zeilen dieser Lane in `/web-status`; fehlerhafte oder lane-fremde Frames, Write-Timeouts und Backend-Close schließen nur die betroffene Lane. | | Ein `websocket-lanes`-Stream wurde geschlossen, Geschwister bleiben aber verbunden | Dies ist die beabsichtigte Fehlergrenze. Prüfen Sie die Message-/Frame-Zeilen dieser Lane in `/web-status`; fehlerhafte oder lane-fremde Frames, Write-Timeouts und Backend-Close schließen nur die betroffene Lane. |
| `/web-status` ist leer | Prüfen Sie, dass `[web.debug].enabled = true` gesetzt ist, wenden Sie die Konfiguration an, wählen Sie ein Fenster innerhalb von `max_window_secs` und erzeugen Sie nach der Policy-Änderung neuen WEB-Datenverkehr. | | `/web-status` ist leer | Prüfen Sie, dass `[web.debug].enabled = true` gesetzt ist, wenden Sie die Konfiguration an, wählen Sie ein Fenster innerhalb von `max_window_secs` und erzeugen Sie nach der Policy-Änderung neuen WEB-Datenverkehr. |
| `https-lanes` funktioniert, Streams blockieren sich aber weiterhin | Prüfen Sie die öffentliche HTTP/2-Aushandlung, die unveränderte Weitergabe von `X-Lane-ID` und genügend TLS-Terminator-Upstream-Verbindungen für parallele private HTTP/1.1-Polls. | | `https-lanes` funktioniert, Streams blockieren sich aber weiterhin | Prüfen Sie die öffentliche HTTP/2-Aushandlung, die unveränderte Weitergabe von `X-Lane-ID` und genügend TLS-Terminator-Upstream-Verbindungen für parallele private HTTP/1.1-Polls. |
+37 -10
View File
@@ -22,11 +22,23 @@ Telemt WEB listener
`-- ordinary or invalid request --> configured decoy site `-- ordinary or invalid request --> configured decoy site
``` ```
Route the complete public vhost to Telemt. Splitting only recognized carrier paths at the TLS terminator would make ordinary and authenticated behavior observably different and would bypass Telemt's decoy policy. Route the complete configured WEB scope to Telemt. With the default empty `base_path`, that scope is the complete public vhost. With a non-empty `base_path`, it is the exact slash-terminated subtree. Splitting only recognized carrier endpoints inside that scope would make ordinary and authenticated behavior observably different and would bypass Telemt's decoy policy.
Let `BASE` mean `/` for an empty `base_path`, or `/<base_path>/` otherwise. The public WEB routes are relative to that exact base:
| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `BASE?bridge=<capability>` | Initial bridge document, or the recovery representation when the recovery `Accept` header and optional bearer are present. |
| `POST`, `DELETE` | `BASEapi/v1/session` | Create or close the parent session. |
| `POST` | `BASEapi/v1/up` | HTTPS carrier uplink. |
| `POST` | `BASEapi/v1/down` | HTTPS carrier downlink. |
| `GET` | `BASEapi/v1/ws` | WebSocket Upgrade. |
`POST BASEapi/v1/diagnostic` is an internal generated-bridge sideband route, not a public client API. Route matching is case-sensitive and byte-exact: there are no aliases, extra-slash variants, percent-encoded slash variants, or query parameters on carrier endpoints. A wrong-shaped request containing a capability or bearer authenticated by the current process receives a local no-store `404`; an unmatched request without authentic carrier material follows the configured decoy. `base_path` changes only these WEB listener routes. It does not prefix the Control API, `/web-status`, or Prometheus metrics.
## Supported client contract ## Supported client contract
- The public endpoint is always `https://HOST:443`. - The public endpoint is `https://HOST:443/` when `base_path` is empty and `https://HOST:443/BASE/` otherwise. The base is case-sensitive and exact; Telemt does not redirect, normalize, or strip it before decoy forwarding.
- `plain` and `dd` 16-byte MTProxy secrets are supported. `ee` FakeTLS secrets are not supported by WEB mode. - `plain` and `dd` 16-byte MTProxy secrets are supported. `ee` FakeTLS secrets are not supported by WEB mode.
- `web.carrier` selects the sole carrier when auto-negotiation is disabled and the final fallback when it is enabled. `https` uses serialized HTTPS uplink and long polling. `https-lanes` uses independent HTTPS sequencing and polling per logical stream. `websocket` uses one ordered WebSocket for all streams. `websocket-lanes` uses one independently owned WebSocket per non-zero logical stream. - `web.carrier` selects the sole carrier when auto-negotiation is disabled and the final fallback when it is enabled. `https` uses serialized HTTPS uplink and long polling. `https-lanes` uses independent HTTPS sequencing and polling per logical stream. `websocket` uses one ordered WebSocket for all streams. `websocket-lanes` uses one independently owned WebSocket per non-zero logical stream.
- Missing `web.carriers` or `web.carriers = false` disables auto-negotiation and learning. A non-empty array enables startup-only sequential negotiation; it never migrates an already committed session. - Missing `web.carriers` or `web.carriers = false` disables auto-negotiation and learning. A non-empty array enables startup-only sequential negotiation; it never migrates an already committed session.
@@ -40,9 +52,10 @@ Telegram Desktop WEB links omit a port because the client requires port 443:
```text ```text
tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef
tg://webproxy?server=proxy.example.com&secret=dd0123456789abcdef0123456789abcdef tg://webproxy?server=proxy.example.com&secret=dd0123456789abcdef0123456789abcdef
tg://webproxy?server=proxy.example.com%2Ftelegram%2Fweb&secret=cAABAgMEBQYHCAkKCwwNDg8
``` ```
Telemt prints links for WEB profiles selected by `[general.links].show` through the existing `telemt::links` log target. Telemt prints links at process startup for WEB profiles selected by `[general.links].show` through the existing `telemt::links` log target. Root links keep the legacy hexadecimal secret. A path link percent-encodes `HOST/BASE` in `server` and uses unpadded base64url of `0x70 || client_secret`, where `client_secret` is the raw 16-byte secret in `plain` mode or `0xdd || secret` in `dd` mode. The users API returns only the raw secret, not a WEB link. `[general.links].public_host` and `public_port` affect native links only and do not override WEB vhost links.
## Prerequisites ## Prerequisites
@@ -81,6 +94,7 @@ http_connection_capacity_action = "drop"
[[web.vhosts]] [[web.vhosts]]
host = "proxy.example.com" host = "proxy.example.com"
base_path = "telegram/web"
public_addr = "203.0.113.10:443" public_addr = "203.0.113.10:443"
[web.vhosts.decoy] [web.vhosts.decoy]
@@ -97,7 +111,9 @@ max_streams_per_session = 64
Accepted-socket overload handling is independently configurable. `drop` preserves the legacy close after `accept(2)`. `respond` writes an empty retryable `503` without parsing a request. `wait` waits outside the accept loop for ordinary connection capacity and then enters normal HTTP handling; timeout writes the same `503`. Both waiting and response writing use `web.timeouts.http_overload_timeout_ms` per phase. `web.limits.max_http_overload_connections` bounds sockets outside ordinary capacity and requires a process restart when changed; the action and timeout are hot-reloadable. Accepted-socket overload handling is independently configurable. `drop` preserves the legacy close after `accept(2)`. `respond` writes an empty retryable `503` without parsing a request. `wait` waits outside the accept loop for ordinary connection capacity and then enters normal HTTP handling; timeout writes the same `503`. Both waiting and response writing use `web.timeouts.http_overload_timeout_ms` per phase. `web.limits.max_http_overload_connections` bounds sockets outside ordinary capacity and requires a process restart when changed; the action and timeout are hot-reloadable.
`decoy_fasttrack_mode` controls only capability work for `GET/HEAD /`. `off` is the default and preserves the legacy full scan without fast-track counters. `shadow` records which structurally impossible requests could bypass the scan but still performs the complete legacy scan. `enforce` bypasses capability work only for `HEAD` or an absent/noncanonical `bridge` query. Every exact canonical `GET /?bridge=<43-character-base64url>` performs a complete scan across all profiles of the selected vhost, for both matches and misses. The setting requires a process restart; reload persists the desired value but reports `web.decoy_fasttrack_mode` as deferred. Fast-track does not protect against adversarial CPU load because a scanner can always submit canonical candidates, and enforce mode may expose a public request-shape timing class, especially with a static decoy. Do not enable enforce without external timing measurements through the production TLS terminator. `base_path` defaults to empty. A non-empty value is at most 128 ASCII bytes and consists of slash-separated `[A-Za-z0-9][A-Za-z0-9_-]*` segments without leading or trailing slash. Root vhosts preserve the v1 capability derivation. Path vhosts use the v2 context over the exact canonical host and base path, so changing case or any segment changes both the route and capability.
`decoy_fasttrack_mode` controls only capability work for `GET/HEAD` at the configured base root. `off` is the default and preserves the legacy full scan without fast-track counters. `shadow` records which structurally impossible requests could bypass the scan but still performs the complete legacy scan. `enforce` bypasses capability work only for `HEAD` or an absent/noncanonical `bridge` query. Every exact canonical bridge GET at the base root performs a complete scan across all profiles of the selected vhost, for both matches and misses. The setting requires a process restart; reload persists the desired value but reports `web.decoy_fasttrack_mode` as deferred. Fast-track does not protect against adversarial CPU load because a scanner can always submit canonical candidates, and enforce mode may expose a public request-shape timing class, especially with a static decoy. Do not enable enforce without external timing measurements through the production TLS terminator.
## Server-side carrier negotiation ## Server-side carrier negotiation
@@ -127,7 +143,7 @@ The bridge emits additive v1 status objects with `state`, `phase`, `reason`, and
Attempts are strictly sequential. Accepted `OPEN` or `DATA` progress commits the chosen carrier immediately and permanently closes the pre-commit replacement boundary. A `409` for an authenticated committed chain echoes the committed metadata and is terminal; it is not permission to advance. Exact `/session` replay is used only while that response is ambiguous. Once an authenticated response has selected a provisional carrier, a transport failure requests the next attempt directly; if the previous probe actually committed, the server answers with the terminal `409` instead of permitting an unsafe replacement. The server's final absolute deadline also bounds a successor response that the client never received. Post-commit in-place carrier switching remains unsupported; a surviving bridge recovers by creating a fresh server session. Attempts are strictly sequential. Accepted `OPEN` or `DATA` progress commits the chosen carrier immediately and permanently closes the pre-commit replacement boundary. A `409` for an authenticated committed chain echoes the committed metadata and is terminal; it is not permission to advance. Exact `/session` replay is used only while that response is ambiguous. Once an authenticated response has selected a provisional carrier, a transport failure requests the next attempt directly; if the previous probe actually committed, the server answers with the terminal `409` instead of permitting an unsafe replacement. The server's final absolute deadline also bounds a successor response that the client never received. Post-commit in-place carrier switching remains unsupported; a surviving bridge recovers by creating a fresh server session.
After commit, an HTTP failure first replays the exact frozen request against the current bearer. A successful replay keeps the current session. WebSocket loss, or a foreground/online/native event after at least `reconnect_grace_secs` of scheduler gap, starts one recovery epoch. The bridge performs exactly one `GET /?bridge=<capability>` with `Accept: application/vnd.telemt.web-recovery+json` and optional current bearer authorization. A positive response is an uncacheable JSON document of at most 1024 bytes containing a fresh bootstrap plus current limits, timeouts, and negotiation policy. Telemt issues that bootstrap before synchronously retiring a matching current session, so recreation remains possible with a one-session capacity. Unknown or already retired bearer authorization receives the same positive representation; malformed recovery headers, disabled admission, pause, drain, and capacity rejection follow the sanitized decoy path. After commit, an HTTP failure first replays the exact frozen request against the current bearer. A successful replay keeps the current session. WebSocket loss, or a foreground/online/native event after at least `reconnect_grace_secs` of scheduler gap, starts one recovery epoch. The bridge performs exactly one GET at its original configured base root with `bridge=<capability>`, `Accept: application/vnd.telemt.web-recovery+json`, and optional current bearer authorization. A positive response is an uncacheable JSON document of at most 1024 bytes containing a fresh bootstrap plus current limits, timeouts, and negotiation policy. Telemt issues that bootstrap before synchronously retiring a matching current session, so recreation remains possible with a one-session capacity. Unknown or already retired bearer authorization receives the same positive representation; malformed recovery headers, disabled admission, pause, drain, and capacity rejection follow the sanitized decoy path.
The recovery epoch has one dual wall/monotonic absolute `bridge_recovery_secs` deadline, a single recovery-document request, and bounded carrier retries with 250 ms through 2 s backoff. Recovery status is repeated at most every 2.5 seconds while active. A fresh incarnation aborts and releases old requests, sockets, lanes, and queues, sends one synthetic `CLOSE` for each still-active native stream, suppresses a second `WELCOME`, and commits only after real carrier progress. Retired stream IDs are retained in a bounded set so valid late frames cannot enter a new stream; the native side must allocate a new stream ID. Frequent native reconnect attempts are valid, but they neither extend the recovery epoch nor retain old incarnation state. Destroying the WebView destroys this recovery owner; a native supervisor must then create a new bridge document. The recovery epoch has one dual wall/monotonic absolute `bridge_recovery_secs` deadline, a single recovery-document request, and bounded carrier retries with 250 ms through 2 s backoff. Recovery status is repeated at most every 2.5 seconds while active. A fresh incarnation aborts and releases old requests, sockets, lanes, and queues, sends one synthetic `CLOSE` for each still-active native stream, suppresses a second `WELCOME`, and commits only after real carrier progress. Retired stream IDs are retained in a bounded set so valid late frames cannot enter a new stream; the native side must allocate a new stream ID. Frequent native reconnect attempts are valid, but they neither extend the recovery epoch nor retain old incarnation state. Destroying the WebView destroys this recovery owner; a native supervisor must then create a new bridge document.
@@ -147,9 +163,9 @@ This removes application-level serialization between WEB streams. Public HTTP/2
All lane queues and resident response bodies remain inside the existing per-session and process-wide byte/item budgets. Telemt additionally limits each lane to `pending_bytes_per_lane` and `pending_items_per_lane`; the generated bridge caps its corresponding queues at 8 MiB and 1024 items. Telemt permits lane long polls to occupy at most half of `web.limits.max_http_handlers`, preserving handler capacity for session creation, uplink, DELETE, and other control work. `https` requires `max_http_handlers >= 2`, and `https-lanes` requires `max_http_handlers >= 4`. All lane queues and resident response bodies remain inside the existing per-session and process-wide byte/item budgets. Telemt additionally limits each lane to `pending_bytes_per_lane` and `pending_items_per_lane`; the generated bridge caps its corresponding queues at 8 MiB and 1024 items. Telemt permits lane long polls to occupy at most half of `web.limits.max_http_handlers`, preserving handler capacity for session creation, uplink, DELETE, and other control work. `https` requires `max_http_handlers >= 2`, and `https-lanes` requires `max_http_handlers >= 4`.
The `/api/v1/up` and `/api/v1/down` paths do not change. In `https-lanes`, every request on those paths carries one canonical decimal `X-Lane-ID`. Uplink sequence starts at `1` and downlink cursor at `0` independently for each lane. Lane zero accepts only session `PONG`; every frame in a non-zero lane must have the same stream ID, and a new lane must begin with `OPEN`. A canonical cursor-zero downlink that reaches Telemt just before its lane `OPEN` waits up to `lane_open_wait_secs` without creating lane state; per-session and process auxiliary permits bound these waits. Expiry returns an empty `204`, while a missing lane with an advanced cursor remains a protocol failure routed through the decoy. After a closed lane's queued and unacknowledged downlink data is drained, Telemt returns an empty response with `X-Lane-Closed: 1`, and the bridge stops polling it. Retries remain byte-identical and replay the original acknowledgement or downlink batch. The `/api/v1/up` and `/api/v1/down` suffixes do not change and are appended to the configured base. In `https-lanes`, every request on those paths carries one canonical decimal `X-Lane-ID`. Uplink sequence starts at `1` and downlink cursor at `0` independently for each lane. Lane zero accepts only session `PONG`; every frame in a non-zero lane must have the same stream ID, and a new lane must begin with `OPEN`. A canonical cursor-zero downlink that reaches Telemt just before its lane `OPEN` waits up to `lane_open_wait_secs` without creating lane state; per-session and process auxiliary permits bound these waits. Expiry returns an empty `204`, while a missing lane with an advanced cursor remains a protocol failure routed through the decoy. After a closed lane's queued and unacknowledged downlink data is drained, Telemt returns an empty response with `X-Lane-Closed: 1`, and the bridge stops polling it. Retries remain byte-identical and replay the original acknowledgement or downlink batch.
Both WebSocket carriers still create and delete the parent session over HTTPS. They then use a strict bodyless `GET /api/v1/ws` Upgrade request. `websocket` offers exactly `tproxy-v1.<session-token>` in `Sec-WebSocket-Protocol`; binary messages are ordered carrier batches, and a protocol, deadline, or connection failure closes the complete parent session. `websocket-lanes` offers exactly `tproxy-lane-v1.<session-token>.<stream-id>`, where the stream ID is canonical decimal in `1..=16777215`. Its first binary message must begin with `OPEN`, every frame must use that stream ID, and failure after upgrade closes only that lane. There is no lane-zero WebSocket: HTTPS carries `HELLO` and `WELCOME`, while RFC 6455 Ping/Pong supplies connection liveness. Both WebSocket carriers still create and delete the parent session over HTTPS. They then use a strict bodyless Upgrade GET at the configured base plus `/api/v1/ws`. `websocket` offers exactly `tproxy-v1.<session-token>` in `Sec-WebSocket-Protocol`; binary messages are ordered carrier batches, and a protocol, deadline, or connection failure closes the complete parent session. `websocket-lanes` offers exactly `tproxy-lane-v1.<session-token>.<stream-id>`, where the stream ID is canonical decimal in `1..=16777215`. Its first binary message must begin with `OPEN`, every frame must use that stream ID, and failure after upgrade closes only that lane. There is no lane-zero WebSocket: HTTPS carries `HELLO` and `WELCOME`, while RFC 6455 Ping/Pong supplies connection liveness.
Before HTTP `101`, a WebSocket-lane reservation binds to the exact process connection and lane incarnation; an accepted `OPEN` transfers ownership to the exact stream incarnation before its backend task can run. A late poll, close, or reservation drop from an older socket cannot acknowledge, close, or release a replacement that reused the same numeric lane ID. Before HTTP `101`, a WebSocket-lane reservation binds to the exact process connection and lane incarnation; an accepted `OPEN` transfers ownership to the exact stream incarnation before its backend task can run. A late poll, close, or reservation drop from an older socket cannot acknowledge, close, or release a replacement that reused the same numeric lane ID.
@@ -218,6 +234,8 @@ server {
Place the `map` in NGINX's `http` context. `client_max_body_size` must be at least `web.limits.max_body_bytes`. Read, send, and client timeouts must exceed both the 25-second default long poll and twice the configured WebSocket liveness interval; 65 seconds covers the defaults. Overwrite, rather than append to, `X-Forwarded-For`. Telemt accepts one parseable IP address; if a trusted terminator omits the header, Telemt falls back to the direct peer address, but per-client limits and source policy then see the terminator rather than the real client. Do not enable upstream retries: the bridge performs byte-identical HTTPS retries, while an established WebSocket is never transparently replayed. Place the `map` in NGINX's `http` context. `client_max_body_size` must be at least `web.limits.max_body_bytes`. Read, send, and client timeouts must exceed both the 25-second default long poll and twice the configured WebSocket liveness interval; 65 seconds covers the defaults. Overwrite, rather than append to, `X-Forwarded-For`. Telemt accepts one parseable IP address; if a trusted terminator omits the header, Telemt falls back to the direct peer address, but per-client limits and source policy then see the terminator rather than the real client. Do not enable upstream retries: the bridge performs byte-identical HTTPS retries, while an established WebSocket is never transparently replayed.
For prefix-only cohosting with `base_path = "telegram/web"`, replace `location /` with `location ^~ /telegram/web/`. Keep `proxy_pass http://telemt_web;` without a URI component and do not add `rewrite`; NGINX must forward the original prefix. Requests outside that subtree may use another site, but every request inside it must go to Telemt. Also define an exact `location = /telegram/web` that uses the ordinary non-WEB site behavior, or proxies unchanged to Telemt's decoy path. Otherwise NGINX can synthesize a slash-appending `301` for the no-slash alias, which is not part of the WEB contract.
Public HTTP/2 is mandatory for `https-lanes`; use the equivalent HTTP/2 directive supported by the installed NGINX release. WebSocket Upgrade requires HTTP/1.1, so the public endpoint must also permit HTTP/1.1 and the private NGINX-to-Telemt hop remains HTTP/1.1. Preserve `Connection`, `Upgrade`, and `Sec-WebSocket-*` exactly as shown. Ensure the upstream connection capacity can sustain the expected simultaneous lane polls or WebSocket lanes; `keepalive` controls the idle pool and is not a concurrency limit. Public HTTP/2 is mandatory for `https-lanes`; use the equivalent HTTP/2 directive supported by the installed NGINX release. WebSocket Upgrade requires HTTP/1.1, so the public endpoint must also permit HTTP/1.1 and the private NGINX-to-Telemt hop remains HTTP/1.1. Preserve `Connection`, `Upgrade`, and `Sec-WebSocket-*` exactly as shown. Ensure the upstream connection capacity can sustain the expected simultaneous lane polls or WebSocket lanes; `keepalive` controls the idle pool and is not a concurrency limit.
### Distinguishing refusal from WEB capacity ### Distinguishing refusal from WEB capacity
@@ -250,7 +268,7 @@ backend telemt_web
server telemt_web_1 127.0.0.1:18080 check server telemt_web_1 127.0.0.1:18080 check
``` ```
The frontend or `defaults` section must also set `timeout client 65s` or longer for the default WebSocket liveness interval. HAProxy's public ALPN must include `h2` for `https-lanes` and `http/1.1` for WebSocket Upgrade. Preserve `Connection`, `Upgrade`, and `Sec-WebSocket-*`; do not rewrite the path, raw query, body, or the `Authorization`, `Content-Type`, `X-Up-Seq`, `X-Down-Cursor`, and `X-Lane-ID` carrier headers. The frontend or `defaults` section must also set `timeout client 65s` or longer for the default WebSocket liveness interval. HAProxy's public ALPN must include `h2` for `https-lanes` and `http/1.1` for WebSocket Upgrade. Preserve `Connection`, `Upgrade`, and `Sec-WebSocket-*`; do not rewrite the path, raw query, body, or the `Authorization`, `Content-Type`, `X-Up-Seq`, `X-Down-Cursor`, and `X-Lane-ID` carrier headers. For prefix-only cohosting, add `acl telemt_web_path path_beg /telegram/web/` and require both the host and path ACLs on `use_backend`; do not remove the prefix.
## Lifecycle and reload behavior ## Lifecycle and reload behavior
@@ -259,6 +277,7 @@ The frontend or `defaults` section must also set `timeout client 65s` or longer
| WEB listener inventory, bind address, and trust policy | Process-owned; restart Telemt. | | WEB listener inventory, bind address, and trust policy | Process-owned; restart Telemt. |
| Any `[web.limits]` value | Process-owned memory/resource contract; restart Telemt. | | Any `[web.limits]` value | Process-owned memory/resource contract; restart Telemt. |
| `web.enabled`, carrier/negotiation policy, `web.debug`, timeouts, vhosts, profiles, and decoys | Applied by the config watcher or a runtime generation reload. | | `web.enabled`, carrier/negotiation policy, `web.debug`, timeouts, vhosts, profiles, and decoys | Applied by the config watcher or a runtime generation reload. |
| A vhost `base_path` change | Atomically switches new HTTP routing and capability derivation. Reissue the generated link. Already upgraded WebSockets and in-flight routed exchanges continue. Later old-base requests carrying a process-authentic bootstrap or session token receive a local no-store `404`; the now-inactive old capability follows ordinary decoy handling. An existing session bearer remains usable only on the new exact base, while an unused bootstrap issued for the old capability cannot create a session on the new base. |
| Operator pause/drain state | Process-owned and ephemeral; survives generation reload, never writes config, and resets to `running` after process restart. | | Operator pause/drain state | Process-owned and ephemeral; survives generation reload, never writes config, and resets to `running` after process restart. |
| Existing HTTP connections and WEB sessions | Keep their acquisition-time HTTP idle limit, carrier candidates, limits, body timeout, closed-token replay lifetime, and absolute session/negotiation deadlines; each issued bridge embeds its request, retry, recovery, and probe-coalescing values. A recovery epoch freezes its current bridge budget, while a successful recovery representation refreshes the policy used by later epochs and the fresh session. WebSocket upgrade, open, write, backpressure, and eviction operations use the parent session's frozen deadlines. Newly issued bridges use the active policy, while new logical streams use the active relay generation. | | Existing HTTP connections and WEB sessions | Keep their acquisition-time HTTP idle limit, carrier candidates, limits, body timeout, closed-token replay lifetime, and absolute session/negotiation deadlines; each issued bridge embeds its request, retry, recovery, and probe-coalescing values. A recovery epoch freezes its current bridge budget, while a successful recovery representation refreshes the policy used by later epochs and the fresh session. WebSocket upgrade, open, write, backpressure, and eviction operations use the parent session's frozen deadlines. Newly issued bridges use the active policy, while new logical streams use the active relay generation. |
| Process shutdown | Captures the latest reloaded `web.timeouts.shutdown_secs` once and shares that single absolute deadline across listener acceptors and connections plus WEB sessions and auxiliary tasks. The waits do not receive sequential per-component budgets. | | Process shutdown | Captures the latest reloaded `web.timeouts.shutdown_secs` once and shares that single absolute deadline across listener acceptors and connections plus WEB sessions and auxiliary tasks. The waits do not receive sequential per-component budgets. |
@@ -269,6 +288,8 @@ HTTP idle accounting protects only explicitly bounded request-body, long-poll, d
An `OPEN` reserves the bounded logical-stream and tuple ownership but does not consume the relay generation's `max_connections` permit. Telemt acquires that permit only after the first inner byte arrives; the frozen first-byte deadline and stream limits bound silent opens, and capacity exhaustion then closes only the affected stream. An `OPEN` reserves the bounded logical-stream and tuple ownership but does not consume the relay generation's `max_connections` permit. Telemt acquires that permit only after the first inner byte arrives; the frozen first-byte deadline and stream limits bound silent opens, and capacity exhaustion then closes only the affected stream.
Treat a live `base_path` change as a credential-bearing route migration. Stop issuing or distribute no new old links, prepare the new link, drain affected sessions when feasible, apply the reload, verify the new route through the public TLS endpoint, and then distribute the new link. Keep both the old and new frontend prefixes routed to Telemt while old capabilities or tokens may still arrive: Telemt must perform the credential-aware local rejection. One vhost cannot accept both bases simultaneously. A true overlap window requires a second hostname/vhost and, when the same host must be retained, a separate process or deployment boundary.
## API management ## API management
WEB configuration, runtime status, and bounded runtime controls share the authenticated API listener. `/web-status` remains a read-only HTML diagnostic view; state-changing operations exist only under `/v1/runtime/web`. WEB configuration, runtime status, and bounded runtime controls share the authenticated API listener. `/web-status` remains a read-only HTML diagnostic view; state-changing operations exist only under `/v1/runtime/web`.
@@ -328,6 +349,7 @@ Enable bounded collection in the owned configuration file:
[web.debug] [web.debug]
enabled = true enabled = true
capture_lifecycle = true capture_lifecycle = true
sideband = true
capture_headers = true capture_headers = true
capture_timings = true capture_timings = true
capture_frames = true capture_frames = true
@@ -344,6 +366,8 @@ The process-owned ring survives runtime generation replacement. Capture-policy c
`body_capture = "off"` omits bodies, `metadata` retains lengths and terminal states, `prefix` retains configured prefixes, and `full` retains recognized carrier bodies up to `web.limits.max_body_bytes`. Ordinary decoy bodies remain limited by `decoy_body_prefix_bytes` even in `full` mode. Queries and raw capabilities are never stored; credential header values are omitted; known WEB capabilities and bearer tokens are scrubbed from captured bodies; the displayed key is a non-secret domain-separated fingerprint. Timing ends at Hyper body polling and does not claim kernel flush or TCP acknowledgment. `body_capture = "off"` omits bodies, `metadata` retains lengths and terminal states, `prefix` retains configured prefixes, and `full` retains recognized carrier bodies up to `web.limits.max_body_bytes`. Ordinary decoy bodies remain limited by `decoy_body_prefix_bytes` even in `full` mode. Queries and raw capabilities are never stored; credential header values are omitted; known WEB capabilities and bearer tokens are scrubbed from captured bodies; the displayed key is a non-secret domain-separated fingerprint. Timing ends at Hyper body polling and does not claim kernel flush or TCP acknowledgment.
Generated-bridge sideband reporting is effective only when `enabled`, `capture_lifecycle`, and `sideband` are all `true`. The policy is hot-reloadable, but only newly issued bridge pages contain the reporter. Each page can report each of the eight fixed events at most once: `runtime_started`, `status_posted`, `hello_received`, `boundary_timeout`, `hello_timeout`, `client_close_before_hello`, `document_unloaded_before_hello`, and `runtime_error_before_hello`. Reports are exact canonical JSON POSTs to `BASEapi/v1/diagnostic`, use the bootstrap bearer without consuming it, and do not participate in carrier framing. Malformed or unauthenticated reports follow the sanitized decoy path.
After an administrator or configuration system atomically updates the TOML file, set `TELEMT_API_AUTH` to the exact value configured in `auth_header` and submit an observable generation reload: After an administrator or configuration system atomically updates the TOML file, set `TELEMT_API_AUTH` to the exact value configured in `auth_header` and submit an observable generation reload:
```bash ```bash
@@ -379,6 +403,7 @@ See the complete [Control API contract](../Architecture/API/API.md) for request
- Never expose the plain HTTP WEB listener to an untrusted network. Enforce the restriction with host firewall rules even when it binds to loopback. - Never expose the plain HTTP WEB listener to an untrusted network. Enforce the restriction with host firewall rules even when it binds to loopback.
- Disable request-target and authorization logging at the TLS terminator, or use a verified redacted format. Raw queries contain bridge capabilities and `Authorization` contains bootstrap or session bearer credentials. - Disable request-target and authorization logging at the TLS terminator, or use a verified redacted format. Raw queries contain bridge capabilities and `Authorization` contains bootstrap or session bearer credentials.
- Telemt rejects a request locally when its URI or headers contain an active capability or any authentic token minted by the current process but the request does not match the carrier contract. Such credentials are never forwarded to the decoy. A merely canonical-looking forged value remains ordinary decoy traffic.
- Keep one stable public address per vhost. If DNS returns several ingress addresses, each deployment must use the address matching its external path. - Keep one stable public address per vhost. If DNS returns several ingress addresses, each deployment must use the address matching its external path.
- Bootstrap and session registries are process-local. A multi-process or multi-host upstream pool requires affinity for the complete vhost: initial and recovery root GET, session creation, uplink, downlink, WebSocket Upgrade, and DELETE. A single Telemt process needs no extra affinity. - Bootstrap and session registries are process-local. A multi-process or multi-host upstream pool requires affinity for the complete vhost: initial and recovery root GET, session creation, uplink, downlink, WebSocket Upgrade, and DELETE. A single Telemt process needs no extra affinity.
- An unused bootstrap survives a configuration reload only when the exact profile identity remains active: host, `public_addr`, user, secret mode, carrier candidates, negotiation deadlines, and capability. Existing created sessions retain their immutable carrier and profile identity and remain lifecycle-bounded. - An unused bootstrap survives a configuration reload only when the exact profile identity remains active: host, `public_addr`, user, secret mode, carrier candidates, negotiation deadlines, and capability. Existing created sessions retain their immutable carrier and profile identity and remain lifecycle-bounded.
@@ -387,7 +412,7 @@ See the complete [Control API contract](../Architecture/API/API.md) for request
## Initial verification ## Initial verification
1. Start the rebuilt Telemt binary with the WEB configuration and confirm that the private listener is bound. 1. Start the rebuilt Telemt binary with the WEB configuration and confirm that the private listener is bound.
2. Confirm through the public TLS endpoint that `GET /`, an unknown path, and an invalid `bridge` query return the configured decoy site. 2. Confirm through the public TLS endpoint that a GET at the configured base root, the no-slash alias, an unknown path inside that base, and an invalid `bridge` query return the intended ordinary site or configured decoy without a synthesized redirect. For prefix-only cohosting, also confirm that the TLS terminator preserves the base path byte-for-byte.
3. Confirm that Telemt receives one parseable `X-Forwarded-For` address and `Host: proxy.example.com` or `Host: proxy.example.com:443`. 3. Confirm that Telemt receives one parseable `X-Forwarded-For` address and `Host: proxy.example.com` or `Host: proxy.example.com:443`.
4. Import the printed `tg://webproxy` link in the intended Telegram Desktop build and establish a proxy connection. 4. Import the printed `tg://webproxy` link in the intended Telegram Desktop build and establish a proxy connection.
5. For `https-lanes`, confirm that the public connection negotiated HTTP/2 and exercise at least two simultaneous logical streams; the private Telemt hop remains HTTP/1.1. 5. For `https-lanes`, confirm that the public connection negotiated HTTP/2 and exercise at least two simultaneous logical streams; the private Telemt hop remains HTTP/1.1.
@@ -402,10 +427,12 @@ See the complete [Control API contract](../Architecture/API/API.md) for request
| --- | --- | | --- | --- |
| WEB configuration is valid on disk but listener behavior did not change | Inspect reload `deferred_process_fields`; listener and `[web.limits]` changes require restart. | | WEB configuration is valid on disk but listener behavior did not change | Inspect reload `deferred_process_fields`; listener and `[web.limits]` changes require restart. |
| Carrier requests reach the decoy | Verify exact vhost, link secret mode, direct proxy CIDR, and one parseable `X-Forwarded-For` value. | | Carrier requests reach the decoy | Verify exact vhost, link secret mode, direct proxy CIDR, and one parseable `X-Forwarded-For` value. |
| A link stopped working after `base_path` changed | Import the newly printed path link and verify that the complete new prefix reaches Telemt unchanged. Existing sessions may recover only through the new exact base; old capabilities cannot be reused. |
| `/telegram/web` redirects to `/telegram/web/` | Add an exact non-WEB handler for the no-slash path. Only the slash-terminated configured subtree belongs to Telemt's WEB contract. |
| A racing `https-lanes` downlink reaches the decoy with `404` | Confirm it starts at `X-Down-Cursor: 0`, preserve `X-Lane-ID`, and set `lane_open_wait_secs` above the observed down-before-`OPEN` skew. Advanced cursors for missing lanes intentionally fail closed. | | A racing `https-lanes` downlink reaches the decoy with `404` | Confirm it starts at `X-Down-Cursor: 0`, preserve `X-Lane-ID`, and set `lane_open_wait_secs` above the observed down-before-`OPEN` skew. Advanced cursors for missing lanes intentionally fail closed. |
| Auto-negotiation advances after traffic was already accepted | This is not valid behavior. Inspect the authenticated `X-Carrier-State` replay and the carrier commit lifecycle row; a committed or healthy response is terminal and requires a new session. | | Auto-negotiation advances after traffic was already accepted | This is not valid behavior. Inspect the authenticated `X-Carrier-State` replay and the carrier commit lifecycle row; a committed or healthy response is terminal and requires a new session. |
| Long polls disconnect near a fixed interval | Raise NGINX/HAProxy client, server, send, and read timeouts above `web.timeouts.long_poll_secs`. | | Long polls disconnect near a fixed interval | Raise NGINX/HAProxy client, server, send, and read timeouts above `web.timeouts.long_poll_secs`. |
| WebSocket Upgrade reaches the decoy instead of returning `101` | Preserve HTTP/1.1 `Connection: Upgrade`, `Upgrade: websocket`, the single exact `Sec-WebSocket-Protocol`, and the canonical bodyless `/api/v1/ws` request. Also check carrier/session compatibility and the process connection reserve. | | WebSocket Upgrade reaches the decoy instead of returning `101` | Preserve HTTP/1.1 `Connection: Upgrade`, `Upgrade: websocket`, the single exact `Sec-WebSocket-Protocol`, and the canonical bodyless request at the configured base plus `/api/v1/ws`. Also check carrier/session compatibility and the process connection reserve. |
| One `websocket-lanes` stream closes while siblings stay connected | This is the intended failure boundary. Inspect that lane's message/frame rows in `/web-status`; malformed, cross-lane, write-timeout, and backend-close paths terminate only the affected lane. | | One `websocket-lanes` stream closes while siblings stay connected | This is the intended failure boundary. Inspect that lane's message/frame rows in `/web-status`; malformed, cross-lane, write-timeout, and backend-close paths terminate only the affected lane. |
| `/web-status` is empty | Confirm `[web.debug].enabled = true`, apply the configuration, select a window within `max_window_secs`, and generate new WEB traffic after the policy change. | | `/web-status` is empty | Confirm `[web.debug].enabled = true`, apply the configuration, select a window within `max_window_secs`, and generate new WEB traffic after the policy change. |
| `https-lanes` works but streams still block each other | Confirm public HTTP/2 negotiation, preserve `X-Lane-ID`, and provide enough TLS-terminator upstream connections for concurrent private HTTP/1.1 polls. | | `https-lanes` works but streams still block each other | Confirm public HTTP/2 negotiation, preserve `X-Lane-ID`, and provide enough TLS-terminator upstream connections for concurrent private HTTP/1.1 polls. |
+77 -18
View File
@@ -22,11 +22,23 @@ WEB-listener Telemt
`-- обычный или некорректный запрос --> настроенный decoy site `-- обычный или некорректный запрос --> настроенный 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-режиме не поддерживаются. - Поддерживаются 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.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 никогда не мигрирует. - Отсутствующий `web.carriers` или `web.carriers = false` отключает auto-negotiation и обучение. Непустой массив включает только стартовый последовательный перебор; уже committed session никогда не мигрирует.
@@ -40,9 +52,10 @@ WEB-listener Telemt
```text ```text
tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef tg://webproxy?server=proxy.example.com&secret=0123456789abcdef0123456789abcdef
tg://webproxy?server=proxy.example.com&secret=dd0123456789abcdef0123456789abcdef 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] [web]
enabled = true enabled = true
carrier = "https-lanes" carrier = "https-lanes"
decoy_fasttrack_mode = "off"
http_connection_capacity_action = "drop"
[[web.vhosts]] [[web.vhosts]]
host = "proxy.example.com" host = "proxy.example.com"
base_path = "telegram/web"
public_addr = "203.0.113.10:443" public_addr = "203.0.113.10:443"
[web.vhosts.decoy] [web.vhosts.decoy]
@@ -93,6 +109,12 @@ max_streams = 512
max_streams_per_session = 64 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 ## Server-side negotiation carrier
Auto-negotiation необязателен и выключен, пока `carriers` не задан явным непустым массивом. Настроенный `carrier` остаётся последним fallback и добавляется ровно один раз, даже если уже присутствует в массиве: Auto-negotiation необязателен и выключен, пока `carriers` не задан явным непустым массивом. Настроенный `carrier` остаётся последним fallback и добавляется ровно один раз, даже если уже присутствует в массиве:
@@ -111,20 +133,29 @@ carrier_health_secs = 30
carrier_learning_secs = 600 carrier_learning_secs = 600
bridge_request_secs = 10 bridge_request_secs = 10
bridge_retry_secs = 90 bridge_retry_secs = 90
bridge_recovery_secs = 15
carrier_probe_coalesce_ms = 0 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`. Сгенерированный 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. 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-протокола. `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`. Все 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. До 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` запрещены. Для 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 статического сайта: Вместо 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’ится прозрачно. Разместите `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. Для `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 ## Терминация TLS на HAProxy
```haproxy ```haproxy
@@ -227,7 +268,7 @@ backend telemt_web
server telemt_web_1 127.0.0.1:18080 check 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 ## Lifecycle и reload
@@ -236,7 +277,9 @@ backend telemt_web
| Состав WEB-listeners, bind address и trust policy | Принадлежат процессу; перезапустите Telemt. | | Состав WEB-listeners, bind address и trust policy | Принадлежат процессу; перезапустите Telemt. |
| Любое значение `[web.limits]` | Process-owned контракт памяти и ресурсов; перезапустите Telemt. | | Любое значение `[web.limits]` | Process-owned контракт памяти и ресурсов; перезапустите Telemt. |
| `web.enabled`, policy carrier/negotiation, `web.debug`, timeouts, vhosts, profiles и decoys | Применяются config watcher или runtime generation reload. | | `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. Последовательные компоненты не получают отдельные полные бюджеты. | | Завершение процесса | Один раз фиксирует последнее применённое значение `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. Каждый 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. `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 ## Управление через API
Конфигурация WEB, статус runtime и bounded runtime-управление доступны на одном аутентифицированном API-listener. `/web-status` остаётся read-only HTML-диагностикой; операции, изменяющие состояние, существуют только под `/v1/runtime/web`. Конфигурация 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`. | | Просмотр bounded серверных WEB request- и lifecycle-деталей | Да, через аутентифицированный `GET /web-status`. |
| Просмотр lifecycle, capacity planes, состояния learning/debug и активных сессий | Да, через `GET /v1/runtime/web/status` и `/v1/runtime/web/sessions`. | | Просмотр lifecycle, capacity planes, состояния learning/debug и активных сессий | Да, через `GET /v1/runtime/web/status` и `/v1/runtime/web/sessions`. |
| Закрытие выбранных активных WEB-сессий | Да, через асинхронную операцию `POST /v1/runtime/web/sessions/close`. | | Закрытие выбранных активных WEB-сессий | Да, через асинхронную операцию `POST /v1/runtime/web/sessions/close`. |
| Приостановка, deadline-drain или возобновление новой WEB-работы | Да, через `/v1/runtime/web/lifecycle/{pause,drain,resume}`. |
| Очистка debug-записей или сброс carrier learning | Да, через соответствующие runtime POST endpoints. | | Очистка debug-записей или сброс carrier learning | Да, через соответствующие runtime POST endpoints. |
| Управление `[access.users]` | Да, через `/v1/users`. Создание пользователя не создаёт WEB-профиль. | | Управление `[access.users]` | Да, через `/v1/users`. Создание пользователя не создаёт WEB-профиль. |
| Отзыв отдельного пользователя | Да. `/v1/users/{username}/disable` немедленно обновляет admission и завершает активные сессии пользователя. | | Отзыв отдельного пользователя | Да. `/v1/users/{username}/disable` немедленно обновляет admission и завершает активные сессии пользователя. |
@@ -276,18 +322,25 @@ API whitelist проверяет непосредственный TCP peer и н
### Статус и управление runtime ### Статус и управление 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. `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. Доступные операции: Каждый 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/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/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 и активные сессии не изменяются. - `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, но никогда не закрывает существующие сессии неявно. Для детерминированного 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-отладка ### Серверная WEB-отладка
Включите bounded сбор в конфигурационном файле, которому принадлежит эта секция: Включите bounded сбор в конфигурационном файле, которому принадлежит эта секция:
@@ -296,6 +349,7 @@ API whitelist проверяет непосредственный TCP peer и н
[web.debug] [web.debug]
enabled = true enabled = true
capture_lifecycle = true capture_lifecycle = true
sideband = true
capture_headers = true capture_headers = true
capture_timings = true capture_timings = true
capture_frames = true capture_frames = true
@@ -306,12 +360,14 @@ default_window_secs = 180
max_window_secs = 3600 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 ёмкость, откладывается до этого перезапуска. 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. `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: После атомарного изменения TOML-файла администратором или системой управления конфигурацией задайте в `TELEMT_API_AUTH` точное значение `auth_header` и отправьте наблюдаемый generation reload:
```bash ```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. - Никогда не публикуйте plain HTTP WEB-listener в недоверенной сети. Закрепите это host firewall rules, даже если listener использует loopback.
- Отключите логирование request target и authorization на TLS-терминаторе либо используйте проверенный формат с редактированием. Raw queries содержат bridge capabilities, а `Authorization` — bootstrap или session bearer credentials. - Отключите логирование 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 должен использовать адрес своего внешнего пути. - Сохраняйте один стабильный публичный адрес на 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. - Неиспользованный 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. - Decoy входит в anti-probing contract. До распространения ссылок проверьте через публичный TLS endpoint его обычный ответ 404 и response timing.
## Первичная проверка ## Первичная проверка
1. Запустите пересобранный Telemt с WEB-конфигурацией и убедитесь, что приватный listener привязан. 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`. 3. Убедитесь, что Telemt получает один корректно разбираемый адрес `X-Forwarded-For` и `Host: proxy.example.com` либо `Host: proxy.example.com:443`.
4. Импортируйте напечатанную ссылку `tg://webproxy` в целевую сборку Telegram Desktop и установите соединение через прокси. 4. Импортируйте напечатанную ссылку `tg://webproxy` в целевую сборку Telegram Desktop и установите соединение через прокси.
5. Для `https-lanes` подтвердите согласование HTTP/2 на публичном connection и проверьте как минимум два одновременных logical streams; приватный hop к Telemt остаётся HTTP/1.1. 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. 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. 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 остаются неизменными. 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]` требуют перезапуска. | | WEB-конфигурация валидна на диске, но поведение listener’а не изменилось | Проверьте `deferred_process_fields`; listener и `[web.limits]` требуют перезапуска. |
| Carrier-запросы попадают в decoy | Проверьте точный vhost, secret mode ссылки, CIDR непосредственного proxy и единственное корректно разбираемое значение `X-Forwarded-For`. | | 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. | | 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 и требует новой сессии. | | 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`. | | 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. | | Один 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. | | `/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. | | `https-lanes` работает, но streams всё ещё блокируют друг друга | Проверьте согласование публичного HTTP/2, сохранение `X-Lane-ID` и достаточное число upstream connections TLS-терминатора для параллельных приватных HTTP/1.1 polls. |