mirror of
https://github.com/telemt/telemt.git
synced 2026-05-19 17:35:43 +03:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e877e45c9 | ||
|
|
0af64a4d0a | ||
|
|
f77e9b8881 | ||
|
|
25ca64de1b | ||
|
|
8895947414 |
@@ -52,10 +52,6 @@ By submitting a PR, you confirm that:
|
||||
|
||||
AI-generated code is treated as **draft** and must be validated like any other external contribution.
|
||||
|
||||
The problem isn’t AI as a tool, but the dilution of responsibility. If the commit history says "Claude/GPT authored this", then who is accountable for the bug? Claude? GPT? Anthropic? OpenAI? Samuel Altman?
|
||||
|
||||
The user who didn’t read the diff? No one? But, in a sensitive system, *"no one"* is an unacceptable maintainer model.
|
||||
|
||||
PRs that look like unverified AI dumps WILL be closed
|
||||
|
||||
---
|
||||
@@ -83,4 +79,4 @@ This includes (but is not limited to):
|
||||
- unverified or low-effort changes
|
||||
- inability to explain the change
|
||||
|
||||
These actions follow the Code of Conduct and are intended to preserve signal, quality, and Telemt's integrity
|
||||
These actions follow the Code of Conduct and are intended to preserve signal, quality, and Telemt's integrity
|
||||
@@ -205,6 +205,8 @@ Notes:
|
||||
| `max_tcp_conns` | `usize` | no | Per-user concurrent TCP limit. |
|
||||
| `expiration_rfc3339` | `string` | no | RFC3339 expiration timestamp. |
|
||||
| `data_quota_bytes` | `u64` | no | Per-user traffic quota. |
|
||||
| `rate_limit_up_bps` | `u64` | no | Per-user upload rate limit in bytes per second. |
|
||||
| `rate_limit_down_bps` | `u64` | no | Per-user download rate limit in bytes per second. |
|
||||
| `max_unique_ips` | `usize` | no | Per-user unique source IP limit. |
|
||||
|
||||
### `PatchUserRequest`
|
||||
@@ -215,6 +217,8 @@ Notes:
|
||||
| `max_tcp_conns` | `usize|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. |
|
||||
| `data_quota_bytes` | `u64|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 bytes per second; `null` removes the upload direction limit. |
|
||||
| `rate_limit_down_bps` | `u64|null` | no | Per-user download rate limit in bytes 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. |
|
||||
|
||||
### `access.user_source_deny` via API
|
||||
@@ -1166,6 +1170,8 @@ An empty request body is accepted and generates a new secret automatically.
|
||||
| `max_tcp_conns` | `usize?` | Optional max concurrent TCP limit. |
|
||||
| `expiration_rfc3339` | `string?` | Optional expiration timestamp. |
|
||||
| `data_quota_bytes` | `u64?` | Optional data quota. |
|
||||
| `rate_limit_up_bps` | `u64?` | Optional upload rate limit in bytes per second. |
|
||||
| `rate_limit_down_bps` | `u64?` | Optional download rate limit in bytes per second. |
|
||||
| `max_unique_ips` | `usize?` | Optional unique IP limit. |
|
||||
| `current_connections` | `u64` | Current live connections. |
|
||||
| `active_unique_ips` | `usize` | Current active unique source IPs. |
|
||||
@@ -1242,6 +1248,12 @@ All mutating endpoints:
|
||||
- Return new `revision` after successful write.
|
||||
- Use process-local mutation lock + atomic write (`tmp + rename`) for config persistence.
|
||||
|
||||
Docker deployment note:
|
||||
- Mutating endpoints require `config.toml` to live inside a writable mounted directory.
|
||||
- Do not mount `config.toml` as a single bind-mounted file when API mutations are enabled; atomic `tmp + rename` writes can fail with `Device or resource busy`.
|
||||
- Mount the config directory instead, for example `./config:/etc/telemt:rw`, and start Telemt with `/etc/telemt/config.toml`.
|
||||
- A read-only single-file mount remains valid only for read-only deployments or when `[server.api].read_only=true`.
|
||||
|
||||
Delete path cleanup guarantees:
|
||||
- Config cleanup removes only the requested username keys.
|
||||
- Runtime unique-IP cleanup removes only this user's limiter and tracked IP state.
|
||||
|
||||
@@ -10,8 +10,6 @@ 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.
|
||||
|
||||
> `Hot-Reload` marks whether a changed value is applied by the config watcher without restarting the process; `✘` means restart is required for runtime effect.
|
||||
|
||||
# Table of contents
|
||||
- [Top-level keys](#top-level-keys)
|
||||
- [general](#general)
|
||||
@@ -31,16 +29,12 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
|
||||
# Top-level keys
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`include`](#include) | `String` (special directive) | — | `✔` |
|
||||
| [`show_link`](#show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) | `✘` |
|
||||
| [`dc_overrides`](#dc_overrides) | `Map<String, String or String[]>` | `{}` | `✘` |
|
||||
| [`default_dc`](#default_dc) | `u8` | — (effective fallback: `2` in ME routing) | `✘` |
|
||||
| [`beobachten`](#beobachten) | `bool` | `true` | `✘` |
|
||||
| [`beobachten_minutes`](#beobachten_minutes) | `u64` | `10` | `✘` |
|
||||
| [`beobachten_flush_secs`](#beobachten_flush_secs) | `u64` | `15` | `✘` |
|
||||
| [`beobachten_file`](#beobachten_file) | `String` | `"cache/beobachten.txt"` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`include`](#include) | `String` (special directive) | — |
|
||||
| [`show_link`](#show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) |
|
||||
| [`dc_overrides`](#dc_overrides) | `Map<String, String or String[]>` | `{}` |
|
||||
| [`default_dc`](#default_dc) | `u8` | — (effective fallback: `2` in ME routing) |
|
||||
|
||||
## include
|
||||
- **Constraints / validation**: Must be a single-line directive in the form `include = "path/to/file.toml"`. Includes are expanded before TOML parsing. Maximum include depth is 10.
|
||||
@@ -85,151 +79,145 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
|
||||
# [general]
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`data_path`](#data_path) | `String` | — | `✘` |
|
||||
| [`quota_state_path`](#quota_state_path) | `Path` | `"telemt.limit.json"` | `✘` |
|
||||
| [`config_strict`](#config_strict) | `bool` | `false` | `✘` |
|
||||
| [`prefer_ipv6`](#prefer_ipv6) | `bool` | `false` | `✘` |
|
||||
| [`fast_mode`](#fast_mode) | `bool` | `true` | `✘` |
|
||||
| [`use_middle_proxy`](#use_middle_proxy) | `bool` | `true` | `✘` |
|
||||
| [`proxy_secret_path`](#proxy_secret_path) | `String` | `"proxy-secret"` | `✘` |
|
||||
| [`proxy_secret_url`](#proxy_secret_url) | `String` | `"https://core.telegram.org/getProxySecret"` | `✘` |
|
||||
| [`proxy_config_v4_cache_path`](#proxy_config_v4_cache_path) | `String` | `"cache/proxy-config-v4.txt"` | `✘` |
|
||||
| [`proxy_config_v4_url`](#proxy_config_v4_url) | `String` | `"https://core.telegram.org/getProxyConfig"` | `✘` |
|
||||
| [`proxy_config_v6_cache_path`](#proxy_config_v6_cache_path) | `String` | `"cache/proxy-config-v6.txt"` | `✘` |
|
||||
| [`proxy_config_v6_url`](#proxy_config_v6_url) | `String` | `"https://core.telegram.org/getProxyConfigV6"` | `✘` |
|
||||
| [`ad_tag`](#ad_tag) | `String` | — | `✔` |
|
||||
| [`middle_proxy_nat_ip`](#middle_proxy_nat_ip) | `IpAddr` | — | `✘` |
|
||||
| [`middle_proxy_nat_probe`](#middle_proxy_nat_probe) | `bool` | `true` | `✘` |
|
||||
| [`middle_proxy_nat_stun`](#middle_proxy_nat_stun) | `String` | — | `✘` |
|
||||
| [`middle_proxy_nat_stun_servers`](#middle_proxy_nat_stun_servers) | `String[]` | `[]` | `✘` |
|
||||
| [`stun_nat_probe_concurrency`](#stun_nat_probe_concurrency) | `usize` | `8` | `✘` |
|
||||
| [`middle_proxy_pool_size`](#middle_proxy_pool_size) | `usize` | `8` | `✘` |
|
||||
| [`middle_proxy_warm_standby`](#middle_proxy_warm_standby) | `usize` | `16` | `✘` |
|
||||
| [`me_init_retry_attempts`](#me_init_retry_attempts) | `u32` | `0` | `✘` |
|
||||
| [`me2dc_fallback`](#me2dc_fallback) | `bool` | `true` | `✘` |
|
||||
| [`me2dc_fast`](#me2dc_fast) | `bool` | `false` | `✘` |
|
||||
| [`me_keepalive_enabled`](#me_keepalive_enabled) | `bool` | `true` | `✘` |
|
||||
| [`me_keepalive_interval_secs`](#me_keepalive_interval_secs) | `u64` | `8` | `✘` |
|
||||
| [`me_keepalive_jitter_secs`](#me_keepalive_jitter_secs) | `u64` | `2` | `✘` |
|
||||
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` |
|
||||
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` |
|
||||
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` |
|
||||
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` |
|
||||
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` |
|
||||
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` |
|
||||
| [`me_reader_route_data_wait_ms`](#me_reader_route_data_wait_ms) | `u64` | `2` | `✔` |
|
||||
| [`me_d2c_flush_batch_max_frames`](#me_d2c_flush_batch_max_frames) | `usize` | `32` | `✔` |
|
||||
| [`me_d2c_flush_batch_max_bytes`](#me_d2c_flush_batch_max_bytes) | `usize` | `131072` | `✔` |
|
||||
| [`me_d2c_flush_batch_max_delay_us`](#me_d2c_flush_batch_max_delay_us) | `u64` | `500` | `✔` |
|
||||
| [`me_d2c_ack_flush_immediate`](#me_d2c_ack_flush_immediate) | `bool` | `true` | `✔` |
|
||||
| [`me_quota_soft_overshoot_bytes`](#me_quota_soft_overshoot_bytes) | `u64` | `65536` | `✔` |
|
||||
| [`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_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` |
|
||||
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` |
|
||||
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` |
|
||||
| [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` |
|
||||
| [`beobachten`](#beobachten) | `bool` | `true` | `✘` |
|
||||
| [`beobachten_minutes`](#beobachten_minutes) | `u64` | `10` | `✘` |
|
||||
| [`beobachten_flush_secs`](#beobachten_flush_secs) | `u64` | `15` | `✘` |
|
||||
| [`beobachten_file`](#beobachten_file) | `String` | `"cache/beobachten.txt"` | `✘` |
|
||||
| [`hardswap`](#hardswap) | `bool` | `true` | `✔` |
|
||||
| [`me_warmup_stagger_enabled`](#me_warmup_stagger_enabled) | `bool` | `true` | `✘` |
|
||||
| [`me_warmup_step_delay_ms`](#me_warmup_step_delay_ms) | `u64` | `500` | `✘` |
|
||||
| [`me_warmup_step_jitter_ms`](#me_warmup_step_jitter_ms) | `u64` | `300` | `✘` |
|
||||
| [`me_reconnect_max_concurrent_per_dc`](#me_reconnect_max_concurrent_per_dc) | `u32` | `8` | `✘` |
|
||||
| [`me_reconnect_backoff_base_ms`](#me_reconnect_backoff_base_ms) | `u64` | `500` | `✘` |
|
||||
| [`me_reconnect_backoff_cap_ms`](#me_reconnect_backoff_cap_ms) | `u64` | `30000` | `✘` |
|
||||
| [`me_reconnect_fast_retry_count`](#me_reconnect_fast_retry_count) | `u32` | `16` | `✘` |
|
||||
| [`me_single_endpoint_shadow_writers`](#me_single_endpoint_shadow_writers) | `u8` | `2` | `✔` |
|
||||
| [`me_single_endpoint_outage_mode_enabled`](#me_single_endpoint_outage_mode_enabled) | `bool` | `true` | `✔` |
|
||||
| [`me_single_endpoint_outage_disable_quarantine`](#me_single_endpoint_outage_disable_quarantine) | `bool` | `true` | `✔` |
|
||||
| [`me_single_endpoint_outage_backoff_min_ms`](#me_single_endpoint_outage_backoff_min_ms) | `u64` | `250` | `✔` |
|
||||
| [`me_single_endpoint_outage_backoff_max_ms`](#me_single_endpoint_outage_backoff_max_ms) | `u64` | `3000` | `✔` |
|
||||
| [`me_single_endpoint_shadow_rotate_every_secs`](#me_single_endpoint_shadow_rotate_every_secs) | `u64` | `900` | `✔` |
|
||||
| [`me_floor_mode`](#me_floor_mode) | `"static"` or `"adaptive"` | `"adaptive"` | `✔` |
|
||||
| [`me_adaptive_floor_idle_secs`](#me_adaptive_floor_idle_secs) | `u64` | `90` | `✔` |
|
||||
| [`me_adaptive_floor_min_writers_single_endpoint`](#me_adaptive_floor_min_writers_single_endpoint) | `u8` | `1` | `✔` |
|
||||
| [`me_adaptive_floor_min_writers_multi_endpoint`](#me_adaptive_floor_min_writers_multi_endpoint) | `u8` | `1` | `✔` |
|
||||
| [`me_adaptive_floor_recover_grace_secs`](#me_adaptive_floor_recover_grace_secs) | `u64` | `180` | `✔` |
|
||||
| [`me_adaptive_floor_writers_per_core_total`](#me_adaptive_floor_writers_per_core_total) | `u16` | `48` | `✔` |
|
||||
| [`me_adaptive_floor_cpu_cores_override`](#me_adaptive_floor_cpu_cores_override) | `u16` | `0` | `✔` |
|
||||
| [`me_adaptive_floor_max_extra_writers_single_per_core`](#me_adaptive_floor_max_extra_writers_single_per_core) | `u16` | `1` | `✔` |
|
||||
| [`me_adaptive_floor_max_extra_writers_multi_per_core`](#me_adaptive_floor_max_extra_writers_multi_per_core) | `u16` | `2` | `✔` |
|
||||
| [`me_adaptive_floor_max_active_writers_per_core`](#me_adaptive_floor_max_active_writers_per_core) | `u16` | `64` | `✔` |
|
||||
| [`me_adaptive_floor_max_warm_writers_per_core`](#me_adaptive_floor_max_warm_writers_per_core) | `u16` | `64` | `✔` |
|
||||
| [`me_adaptive_floor_max_active_writers_global`](#me_adaptive_floor_max_active_writers_global) | `u32` | `256` | `✔` |
|
||||
| [`me_adaptive_floor_max_warm_writers_global`](#me_adaptive_floor_max_warm_writers_global) | `u32` | `256` | `✔` |
|
||||
| [`upstream_connect_retry_attempts`](#upstream_connect_retry_attempts) | `u32` | `2` | `✘` |
|
||||
| [`upstream_connect_retry_backoff_ms`](#upstream_connect_retry_backoff_ms) | `u64` | `100` | `✘` |
|
||||
| [`upstream_connect_budget_ms`](#upstream_connect_budget_ms) | `u64` | `3000` | `✘` |
|
||||
| [`tg_connect`](#tg_connect) | `u64` | `10` | `✘` |
|
||||
| [`upstream_unhealthy_fail_threshold`](#upstream_unhealthy_fail_threshold) | `u32` | `5` | `✘` |
|
||||
| [`upstream_connect_failfast_hard_errors`](#upstream_connect_failfast_hard_errors) | `bool` | `false` | `✘` |
|
||||
| [`stun_iface_mismatch_ignore`](#stun_iface_mismatch_ignore) | `bool` | `false` | `✘` |
|
||||
| [`unknown_dc_log_path`](#unknown_dc_log_path) | `String` | `"unknown-dc.txt"` | `✘` |
|
||||
| [`unknown_dc_file_log_enabled`](#unknown_dc_file_log_enabled) | `bool` | `false` | `✘` |
|
||||
| [`log_level`](#log_level) | `"debug"`, `"verbose"`, `"normal"`, or `"silent"` | `"normal"` | `✔` |
|
||||
| [`disable_colors`](#disable_colors) | `bool` | `false` | `✘` |
|
||||
| [`me_socks_kdf_policy`](#me_socks_kdf_policy) | `"strict"` or `"compat"` | `"strict"` | `✔` |
|
||||
| [`me_route_backpressure_enabled`](#me_route_backpressure_enabled) | `bool` | `false` | `✔` |
|
||||
| [`me_route_fairshare_enabled`](#me_route_fairshare_enabled) | `bool` | `false` | `✔` |
|
||||
| [`me_route_backpressure_base_timeout_ms`](#me_route_backpressure_base_timeout_ms) | `u64` | `25` | `✔` |
|
||||
| [`me_route_backpressure_high_timeout_ms`](#me_route_backpressure_high_timeout_ms) | `u64` | `120` | `✔` |
|
||||
| [`me_route_backpressure_high_watermark_pct`](#me_route_backpressure_high_watermark_pct) | `u8` | `80` | `✔` |
|
||||
| [`me_health_interval_ms_unhealthy`](#me_health_interval_ms_unhealthy) | `u64` | `1000` | `✔` |
|
||||
| [`me_health_interval_ms_healthy`](#me_health_interval_ms_healthy) | `u64` | `3000` | `✔` |
|
||||
| [`me_admission_poll_ms`](#me_admission_poll_ms) | `u64` | `1000` | `✔` |
|
||||
| [`me_warn_rate_limit_ms`](#me_warn_rate_limit_ms) | `u64` | `5000` | `✔` |
|
||||
| [`me_route_no_writer_mode`](#me_route_no_writer_mode) | `"async_recovery_failfast"`, `"inline_recovery_legacy"`, or `"hybrid_async_persistent"` | `"hybrid_async_persistent"` | `✘` |
|
||||
| [`me_route_no_writer_wait_ms`](#me_route_no_writer_wait_ms) | `u64` | `250` | `✘` |
|
||||
| [`me_route_hybrid_max_wait_ms`](#me_route_hybrid_max_wait_ms) | `u64` | `3000` | `✘` |
|
||||
| [`me_route_blocking_send_timeout_ms`](#me_route_blocking_send_timeout_ms) | `u64` | `250` | `✘` |
|
||||
| [`me_route_inline_recovery_attempts`](#me_route_inline_recovery_attempts) | `u32` | `3` | `✘` |
|
||||
| [`me_route_inline_recovery_wait_ms`](#me_route_inline_recovery_wait_ms) | `u64` | `3000` | `✘` |
|
||||
| [`fast_mode_min_tls_record`](#fast_mode_min_tls_record) | `usize` | `0` | `✘` |
|
||||
| [`update_every`](#update_every) | `u64` | `300` | `✔` |
|
||||
| [`me_reinit_every_secs`](#me_reinit_every_secs) | `u64` | `900` | `✔` |
|
||||
| [`me_hardswap_warmup_delay_min_ms`](#me_hardswap_warmup_delay_min_ms) | `u64` | `1000` | `✔` |
|
||||
| [`me_hardswap_warmup_delay_max_ms`](#me_hardswap_warmup_delay_max_ms) | `u64` | `2000` | `✔` |
|
||||
| [`me_hardswap_warmup_extra_passes`](#me_hardswap_warmup_extra_passes) | `u8` | `3` | `✔` |
|
||||
| [`me_hardswap_warmup_pass_backoff_base_ms`](#me_hardswap_warmup_pass_backoff_base_ms) | `u64` | `500` | `✔` |
|
||||
| [`me_config_stable_snapshots`](#me_config_stable_snapshots) | `u8` | `2` | `✔` |
|
||||
| [`me_config_apply_cooldown_secs`](#me_config_apply_cooldown_secs) | `u64` | `300` | `✔` |
|
||||
| [`me_snapshot_require_http_2xx`](#me_snapshot_require_http_2xx) | `bool` | `true` | `✔` |
|
||||
| [`me_snapshot_reject_empty_map`](#me_snapshot_reject_empty_map) | `bool` | `true` | `✔` |
|
||||
| [`me_snapshot_min_proxy_for_lines`](#me_snapshot_min_proxy_for_lines) | `u32` | `1` | `✔` |
|
||||
| [`proxy_secret_stable_snapshots`](#proxy_secret_stable_snapshots) | `u8` | `2` | `✔` |
|
||||
| [`proxy_secret_rotate_runtime`](#proxy_secret_rotate_runtime) | `bool` | `true` | `✔` |
|
||||
| [`me_secret_atomic_snapshot`](#me_secret_atomic_snapshot) | `bool` | `true` | `✔` |
|
||||
| [`proxy_secret_len_max`](#proxy_secret_len_max) | `usize` | `256` | `✔` |
|
||||
| [`me_pool_drain_ttl_secs`](#me_pool_drain_ttl_secs) | `u64` | `90` | `✔` |
|
||||
| [`me_instadrain`](#me_instadrain) | `bool` | `false` | `✔` |
|
||||
| [`me_pool_drain_threshold`](#me_pool_drain_threshold) | `u64` | `32` | `✔` |
|
||||
| [`me_pool_drain_soft_evict_enabled`](#me_pool_drain_soft_evict_enabled) | `bool` | `true` | `✘` |
|
||||
| [`me_pool_drain_soft_evict_grace_secs`](#me_pool_drain_soft_evict_grace_secs) | `u64` | `10` | `✘` |
|
||||
| [`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_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_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` |
|
||||
| [`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` | `✔` |
|
||||
| [`proxy_secret_auto_reload_secs`](#proxy_secret_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_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` | `✘` |
|
||||
| [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` | `✔` |
|
||||
| [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` | `✔` |
|
||||
| [`me_writer_pick_mode`](#me_writer_pick_mode) | `"sorted_rr"` or `"p2c"` | `"p2c"` | `✔` |
|
||||
| [`me_writer_pick_sample_size`](#me_writer_pick_sample_size) | `u8` | `3` | `✔` |
|
||||
| [`ntp_check`](#ntp_check) | `bool` | `true` | `✘` |
|
||||
| [`ntp_servers`](#ntp_servers) | `String[]` | `["pool.ntp.org"]` | `✘` |
|
||||
| [`auto_degradation_enabled`](#auto_degradation_enabled) | `bool` | `true` | `✘` |
|
||||
| [`degradation_min_unavailable_dc_groups`](#degradation_min_unavailable_dc_groups) | `u8` | `2` | `✘` |
|
||||
| [`rst_on_close`](#rst_on_close) | `"off"`, `"errors"`, or `"always"` | `"off"` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`data_path`](#data_path) | `String` | — |
|
||||
| [`prefer_ipv6`](#prefer_ipv6) | `bool` | `false` |
|
||||
| [`fast_mode`](#fast_mode) | `bool` | `true` |
|
||||
| [`use_middle_proxy`](#use_middle_proxy) | `bool` | `true` |
|
||||
| [`proxy_secret_path`](#proxy_secret_path) | `String` | `"proxy-secret"` |
|
||||
| [`proxy_config_v4_cache_path`](#proxy_config_v4_cache_path) | `String` | `"cache/proxy-config-v4.txt"` |
|
||||
| [`proxy_config_v6_cache_path`](#proxy_config_v6_cache_path) | `String` | `"cache/proxy-config-v6.txt"` |
|
||||
| [`ad_tag`](#ad_tag) | `String` | — |
|
||||
| [`middle_proxy_nat_ip`](#middle_proxy_nat_ip) | `IpAddr` | — |
|
||||
| [`middle_proxy_nat_probe`](#middle_proxy_nat_probe) | `bool` | `true` |
|
||||
| [`middle_proxy_nat_stun`](#middle_proxy_nat_stun) | `String` | — |
|
||||
| [`middle_proxy_nat_stun_servers`](#middle_proxy_nat_stun_servers) | `String[]` | `[]` |
|
||||
| [`stun_nat_probe_concurrency`](#stun_nat_probe_concurrency) | `usize` | `8` |
|
||||
| [`middle_proxy_pool_size`](#middle_proxy_pool_size) | `usize` | `8` |
|
||||
| [`middle_proxy_warm_standby`](#middle_proxy_warm_standby) | `usize` | `16` |
|
||||
| [`me_init_retry_attempts`](#me_init_retry_attempts) | `u32` | `0` |
|
||||
| [`me2dc_fallback`](#me2dc_fallback) | `bool` | `true` |
|
||||
| [`me2dc_fast`](#me2dc_fast) | `bool` | `false` |
|
||||
| [`me_keepalive_enabled`](#me_keepalive_enabled) | `bool` | `true` |
|
||||
| [`me_keepalive_interval_secs`](#me_keepalive_interval_secs) | `u64` | `8` |
|
||||
| [`me_keepalive_jitter_secs`](#me_keepalive_jitter_secs) | `u64` | `2` |
|
||||
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` |
|
||||
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` |
|
||||
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` |
|
||||
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` |
|
||||
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` |
|
||||
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` |
|
||||
| [`me_reader_route_data_wait_ms`](#me_reader_route_data_wait_ms) | `u64` | `2` |
|
||||
| [`me_d2c_flush_batch_max_frames`](#me_d2c_flush_batch_max_frames) | `usize` | `32` |
|
||||
| [`me_d2c_flush_batch_max_bytes`](#me_d2c_flush_batch_max_bytes) | `usize` | `131072` |
|
||||
| [`me_d2c_flush_batch_max_delay_us`](#me_d2c_flush_batch_max_delay_us) | `u64` | `500` |
|
||||
| [`me_d2c_ack_flush_immediate`](#me_d2c_ack_flush_immediate) | `bool` | `true` |
|
||||
| [`me_quota_soft_overshoot_bytes`](#me_quota_soft_overshoot_bytes) | `u64` | `65536` |
|
||||
| [`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_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` |
|
||||
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` |
|
||||
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` |
|
||||
| [`desync_all_full`](#desync_all_full) | `bool` | `false` |
|
||||
| [`beobachten`](#beobachten) | `bool` | `true` |
|
||||
| [`beobachten_minutes`](#beobachten_minutes) | `u64` | `10` |
|
||||
| [`beobachten_flush_secs`](#beobachten_flush_secs) | `u64` | `15` |
|
||||
| [`beobachten_file`](#beobachten_file) | `String` | `"cache/beobachten.txt"` |
|
||||
| [`hardswap`](#hardswap) | `bool` | `true` |
|
||||
| [`me_warmup_stagger_enabled`](#me_warmup_stagger_enabled) | `bool` | `true` |
|
||||
| [`me_warmup_step_delay_ms`](#me_warmup_step_delay_ms) | `u64` | `500` |
|
||||
| [`me_warmup_step_jitter_ms`](#me_warmup_step_jitter_ms) | `u64` | `300` |
|
||||
| [`me_reconnect_max_concurrent_per_dc`](#me_reconnect_max_concurrent_per_dc) | `u32` | `8` |
|
||||
| [`me_reconnect_backoff_base_ms`](#me_reconnect_backoff_base_ms) | `u64` | `500` |
|
||||
| [`me_reconnect_backoff_cap_ms`](#me_reconnect_backoff_cap_ms) | `u64` | `30000` |
|
||||
| [`me_reconnect_fast_retry_count`](#me_reconnect_fast_retry_count) | `u32` | `16` |
|
||||
| [`me_single_endpoint_shadow_writers`](#me_single_endpoint_shadow_writers) | `u8` | `2` |
|
||||
| [`me_single_endpoint_outage_mode_enabled`](#me_single_endpoint_outage_mode_enabled) | `bool` | `true` |
|
||||
| [`me_single_endpoint_outage_disable_quarantine`](#me_single_endpoint_outage_disable_quarantine) | `bool` | `true` |
|
||||
| [`me_single_endpoint_outage_backoff_min_ms`](#me_single_endpoint_outage_backoff_min_ms) | `u64` | `250` |
|
||||
| [`me_single_endpoint_outage_backoff_max_ms`](#me_single_endpoint_outage_backoff_max_ms) | `u64` | `3000` |
|
||||
| [`me_single_endpoint_shadow_rotate_every_secs`](#me_single_endpoint_shadow_rotate_every_secs) | `u64` | `900` |
|
||||
| [`me_floor_mode`](#me_floor_mode) | `"static"` or `"adaptive"` | `"adaptive"` |
|
||||
| [`me_adaptive_floor_idle_secs`](#me_adaptive_floor_idle_secs) | `u64` | `90` |
|
||||
| [`me_adaptive_floor_min_writers_single_endpoint`](#me_adaptive_floor_min_writers_single_endpoint) | `u8` | `1` |
|
||||
| [`me_adaptive_floor_min_writers_multi_endpoint`](#me_adaptive_floor_min_writers_multi_endpoint) | `u8` | `1` |
|
||||
| [`me_adaptive_floor_recover_grace_secs`](#me_adaptive_floor_recover_grace_secs) | `u64` | `180` |
|
||||
| [`me_adaptive_floor_writers_per_core_total`](#me_adaptive_floor_writers_per_core_total) | `u16` | `48` |
|
||||
| [`me_adaptive_floor_cpu_cores_override`](#me_adaptive_floor_cpu_cores_override) | `u16` | `0` |
|
||||
| [`me_adaptive_floor_max_extra_writers_single_per_core`](#me_adaptive_floor_max_extra_writers_single_per_core) | `u16` | `1` |
|
||||
| [`me_adaptive_floor_max_extra_writers_multi_per_core`](#me_adaptive_floor_max_extra_writers_multi_per_core) | `u16` | `2` |
|
||||
| [`me_adaptive_floor_max_active_writers_per_core`](#me_adaptive_floor_max_active_writers_per_core) | `u16` | `64` |
|
||||
| [`me_adaptive_floor_max_warm_writers_per_core`](#me_adaptive_floor_max_warm_writers_per_core) | `u16` | `64` |
|
||||
| [`me_adaptive_floor_max_active_writers_global`](#me_adaptive_floor_max_active_writers_global) | `u32` | `256` |
|
||||
| [`me_adaptive_floor_max_warm_writers_global`](#me_adaptive_floor_max_warm_writers_global) | `u32` | `256` |
|
||||
| [`upstream_connect_retry_attempts`](#upstream_connect_retry_attempts) | `u32` | `2` |
|
||||
| [`upstream_connect_retry_backoff_ms`](#upstream_connect_retry_backoff_ms) | `u64` | `100` |
|
||||
| [`upstream_connect_budget_ms`](#upstream_connect_budget_ms) | `u64` | `3000` |
|
||||
| [`upstream_unhealthy_fail_threshold`](#upstream_unhealthy_fail_threshold) | `u32` | `5` |
|
||||
| [`upstream_connect_failfast_hard_errors`](#upstream_connect_failfast_hard_errors) | `bool` | `false` |
|
||||
| [`stun_iface_mismatch_ignore`](#stun_iface_mismatch_ignore) | `bool` | `false` |
|
||||
| [`unknown_dc_log_path`](#unknown_dc_log_path) | `String` | `"unknown-dc.txt"` |
|
||||
| [`unknown_dc_file_log_enabled`](#unknown_dc_file_log_enabled) | `bool` | `false` |
|
||||
| [`log_level`](#log_level) | `"debug"`, `"verbose"`, `"normal"`, or `"silent"` | `"normal"` |
|
||||
| [`disable_colors`](#disable_colors) | `bool` | `false` |
|
||||
| [`me_socks_kdf_policy`](#me_socks_kdf_policy) | `"strict"` or `"compat"` | `"strict"` |
|
||||
| [`me_route_backpressure_enabled`](#me_route_backpressure_enabled) | `bool` | `false` |
|
||||
| [`me_route_fairshare_enabled`](#me_route_fairshare_enabled) | `bool` | `false` |
|
||||
| [`me_route_backpressure_base_timeout_ms`](#me_route_backpressure_base_timeout_ms) | `u64` | `25` |
|
||||
| [`me_route_backpressure_high_timeout_ms`](#me_route_backpressure_high_timeout_ms) | `u64` | `120` |
|
||||
| [`me_route_backpressure_high_watermark_pct`](#me_route_backpressure_high_watermark_pct) | `u8` | `80` |
|
||||
| [`me_health_interval_ms_unhealthy`](#me_health_interval_ms_unhealthy) | `u64` | `1000` |
|
||||
| [`me_health_interval_ms_healthy`](#me_health_interval_ms_healthy) | `u64` | `3000` |
|
||||
| [`me_admission_poll_ms`](#me_admission_poll_ms) | `u64` | `1000` |
|
||||
| [`me_warn_rate_limit_ms`](#me_warn_rate_limit_ms) | `u64` | `5000` |
|
||||
| [`me_route_no_writer_mode`](#me_route_no_writer_mode) | `"async_recovery_failfast"`, `"inline_recovery_legacy"`, or `"hybrid_async_persistent"` | `"hybrid_async_persistent"` |
|
||||
| [`me_route_no_writer_wait_ms`](#me_route_no_writer_wait_ms) | `u64` | `250` |
|
||||
| [`me_route_hybrid_max_wait_ms`](#me_route_hybrid_max_wait_ms) | `u64` | `3000` |
|
||||
| [`me_route_blocking_send_timeout_ms`](#me_route_blocking_send_timeout_ms) | `u64` | `250` |
|
||||
| [`me_route_inline_recovery_attempts`](#me_route_inline_recovery_attempts) | `u32` | `3` |
|
||||
| [`me_route_inline_recovery_wait_ms`](#me_route_inline_recovery_wait_ms) | `u64` | `3000` |
|
||||
| [`fast_mode_min_tls_record`](#fast_mode_min_tls_record) | `usize` | `0` |
|
||||
| [`update_every`](#update_every) | `u64` | `300` |
|
||||
| [`me_reinit_every_secs`](#me_reinit_every_secs) | `u64` | `900` |
|
||||
| [`me_hardswap_warmup_delay_min_ms`](#me_hardswap_warmup_delay_min_ms) | `u64` | `1000` |
|
||||
| [`me_hardswap_warmup_delay_max_ms`](#me_hardswap_warmup_delay_max_ms) | `u64` | `2000` |
|
||||
| [`me_hardswap_warmup_extra_passes`](#me_hardswap_warmup_extra_passes) | `u8` | `3` |
|
||||
| [`me_hardswap_warmup_pass_backoff_base_ms`](#me_hardswap_warmup_pass_backoff_base_ms) | `u64` | `500` |
|
||||
| [`me_config_stable_snapshots`](#me_config_stable_snapshots) | `u8` | `2` |
|
||||
| [`me_config_apply_cooldown_secs`](#me_config_apply_cooldown_secs) | `u64` | `300` |
|
||||
| [`me_snapshot_require_http_2xx`](#me_snapshot_require_http_2xx) | `bool` | `true` |
|
||||
| [`me_snapshot_reject_empty_map`](#me_snapshot_reject_empty_map) | `bool` | `true` |
|
||||
| [`me_snapshot_min_proxy_for_lines`](#me_snapshot_min_proxy_for_lines) | `u32` | `1` |
|
||||
| [`proxy_secret_stable_snapshots`](#proxy_secret_stable_snapshots) | `u8` | `2` |
|
||||
| [`proxy_secret_rotate_runtime`](#proxy_secret_rotate_runtime) | `bool` | `true` |
|
||||
| [`me_secret_atomic_snapshot`](#me_secret_atomic_snapshot) | `bool` | `true` |
|
||||
| [`proxy_secret_len_max`](#proxy_secret_len_max) | `usize` | `256` |
|
||||
| [`me_pool_drain_ttl_secs`](#me_pool_drain_ttl_secs) | `u64` | `90` |
|
||||
| [`me_instadrain`](#me_instadrain) | `bool` | `false` |
|
||||
| [`me_pool_drain_threshold`](#me_pool_drain_threshold) | `u64` | `32` |
|
||||
| [`me_pool_drain_soft_evict_enabled`](#me_pool_drain_soft_evict_enabled) | `bool` | `true` |
|
||||
| [`me_pool_drain_soft_evict_grace_secs`](#me_pool_drain_soft_evict_grace_secs) | `u64` | `10` |
|
||||
| [`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_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_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` |
|
||||
| [`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` |
|
||||
| [`proxy_secret_auto_reload_secs`](#proxy_secret_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_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` |
|
||||
| [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` |
|
||||
| [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` |
|
||||
| [`me_writer_pick_mode`](#me_writer_pick_mode) | `"sorted_rr"` or `"p2c"` | `"p2c"` |
|
||||
| [`me_writer_pick_sample_size`](#me_writer_pick_sample_size) | `u8` | `3` |
|
||||
| [`ntp_check`](#ntp_check) | `bool` | `true` |
|
||||
| [`ntp_servers`](#ntp_servers) | `String[]` | `["pool.ntp.org"]` |
|
||||
| [`auto_degradation_enabled`](#auto_degradation_enabled) | `bool` | `true` |
|
||||
| [`degradation_min_unavailable_dc_groups`](#degradation_min_unavailable_dc_groups) | `u8` | `2` |
|
||||
| [`rst_on_close`](#rst_on_close) | `"off"`, `"errors"`, or `"always"` | `"off"` |
|
||||
|
||||
## data_path
|
||||
- **Constraints / validation**: `String` (optional).
|
||||
@@ -240,24 +228,6 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
[general]
|
||||
data_path = "/var/lib/telemt"
|
||||
```
|
||||
## quota_state_path
|
||||
- **Constraints / validation**: `Path`. Relative paths are resolved from the process working directory.
|
||||
- **Description**: JSON state file used to persist runtime per-user quota consumption.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
quota_state_path = "telemt.limit.json"
|
||||
```
|
||||
## config_strict
|
||||
- **Constraints / validation**: `bool`.
|
||||
- **Description**: Rejects unknown TOML keys during config load. Startup fails fast; hot-reload rejects the new snapshot and keeps the current config.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
config_strict = true
|
||||
```
|
||||
## prefer_ipv6
|
||||
- **Constraints / validation**: Deprecated. Use `network.prefer`.
|
||||
- **Description**: Deprecated legacy IPv6 preference flag migrated to `network.prefer`.
|
||||
@@ -935,15 +905,6 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
[general]
|
||||
upstream_connect_budget_ms = 3000
|
||||
```
|
||||
## tg_connect
|
||||
- **Constraints / validation**: Must be `> 0` (seconds).
|
||||
- **Description**: Upstream Telegram connect timeout.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
tg_connect = 10
|
||||
```
|
||||
## upstream_unhealthy_fail_threshold
|
||||
- **Constraints / validation**: Must be `> 0`.
|
||||
- **Description**: Consecutive failed requests before upstream is marked unhealthy.
|
||||
@@ -1559,11 +1520,11 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [general.modes]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`classic`](#classic) | `bool` | `false` | `✘` |
|
||||
| [`secure`](#secure) | `bool` | `false` | `✘` |
|
||||
| [`tls`](#tls) | `bool` | `true` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`classic`](#classic) | `bool` | `false` |
|
||||
| [`secure`](#secure) | `bool` | `false` |
|
||||
| [`tls`](#tls) | `bool` | `true` |
|
||||
|
||||
## classic
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -1597,11 +1558,11 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [general.links]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` | `✘` |
|
||||
| [`public_host`](#public_host) | `String` | — | `✘` |
|
||||
| [`public_port`](#public_port) | `u16` | — | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` |
|
||||
| [`public_host`](#public_host) | `String` | — |
|
||||
| [`public_port`](#public_port) | `u16` | — |
|
||||
|
||||
## show
|
||||
- **Constraints / validation**: `"*"` or `String[]`. An empty array means "show none".
|
||||
@@ -1637,11 +1598,11 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [general.telemetry]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`core_enabled`](#core_enabled) | `bool` | `true` | `✔` |
|
||||
| [`user_enabled`](#user_enabled) | `bool` | `true` | `✔` |
|
||||
| [`me_level`](#me_level) | `"silent"`, `"normal"`, or `"debug"` | `"normal"` | `✔` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`core_enabled`](#core_enabled) | `bool` | `true` |
|
||||
| [`user_enabled`](#user_enabled) | `bool` | `true` |
|
||||
| [`me_level`](#me_level) | `"silent"`, `"normal"`, or `"debug"` | `"normal"` |
|
||||
|
||||
## core_enabled
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -1675,18 +1636,18 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [network]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`ipv4`](#ipv4) | `bool` | `true` | `✘` |
|
||||
| [`ipv6`](#ipv6) | `bool` | `false` | `✘` |
|
||||
| [`prefer`](#prefer) | `u8` | `4` | `✘` |
|
||||
| [`multipath`](#multipath) | `bool` | `false` | `✘` |
|
||||
| [`stun_use`](#stun_use) | `bool` | `true` | `✘` |
|
||||
| [`stun_servers`](#stun_servers) | `String[]` | Built-in STUN list (13 hosts) | `✘` |
|
||||
| [`stun_tcp_fallback`](#stun_tcp_fallback) | `bool` | `true` | `✘` |
|
||||
| [`http_ip_detect_urls`](#http_ip_detect_urls) | `String[]` | `["https://ifconfig.me/ip", "https://api.ipify.org"]` | `✘` |
|
||||
| [`cache_public_ip_path`](#cache_public_ip_path) | `String` | `"cache/public_ip.txt"` | `✘` |
|
||||
| [`dns_overrides`](#dns_overrides) | `String[]` | `[]` | `✔` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`ipv4`](#ipv4) | `bool` | `true` |
|
||||
| [`ipv6`](#ipv6) | `bool` | `false` |
|
||||
| [`prefer`](#prefer) | `u8` | `4` |
|
||||
| [`multipath`](#multipath) | `bool` | `false` |
|
||||
| [`stun_use`](#stun_use) | `bool` | `true` |
|
||||
| [`stun_servers`](#stun_servers) | `String[]` | Built-in STUN list (13 hosts) |
|
||||
| [`stun_tcp_fallback`](#stun_tcp_fallback) | `bool` | `true` |
|
||||
| [`http_ip_detect_urls`](#http_ip_detect_urls) | `String[]` | `["https://ifconfig.me/ip", "https://api.ipify.org"]` |
|
||||
| [`cache_public_ip_path`](#cache_public_ip_path) | `String` | `"cache/public_ip.txt"` |
|
||||
| [`dns_overrides`](#dns_overrides) | `String[]` | `[]` |
|
||||
|
||||
## ipv4
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -1796,27 +1757,23 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [server]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`port`](#port) | `u16` | `443` | `✘` |
|
||||
| [`listen_addr_ipv4`](#listen_addr_ipv4) | `String` | `"0.0.0.0"` | `✘` |
|
||||
| [`listen_addr_ipv6`](#listen_addr_ipv6) | `String` | `"::"` | `✘` |
|
||||
| [`listen_unix_sock`](#listen_unix_sock) | `String` | — | `✘` |
|
||||
| [`listen_unix_sock_perm`](#listen_unix_sock_perm) | `String` | — | `✘` |
|
||||
| [`listen_tcp`](#listen_tcp) | `bool` | — (auto) | `✘` |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | `false` | `✘` |
|
||||
| [`proxy_protocol_header_timeout_ms`](#proxy_protocol_header_timeout_ms) | `u64` | `500` | `✘` |
|
||||
| [`proxy_protocol_trusted_cidrs`](#proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` | `✘` |
|
||||
| [`metrics_port`](#metrics_port) | `u16` | — | `✘` |
|
||||
| [`metrics_listen`](#metrics_listen) | `String` | — | `✘` |
|
||||
| [`metrics_whitelist`](#metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` | `✘` |
|
||||
| [`api`](#serverapi) | `Table` | built-in defaults | `✘` |
|
||||
| [`admin_api`](#serverapi) | `Table` | alias for `api` | `✘` |
|
||||
| [`listeners`](#serverlisteners) | `Table[]` | derived from legacy listener fields | `✘` |
|
||||
| [`max_connections`](#max_connections) | `u32` | `10000` | `✘` |
|
||||
| [`accept_permit_timeout_ms`](#accept_permit_timeout_ms) | `u64` | `250` | `✘` |
|
||||
| [`listen_backlog`](#listen_backlog) | `u32` | `1024` | `✘` |
|
||||
| [`conntrack_control`](#serverconntrack_control) | `Table` | built-in defaults | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`port`](#port) | `u16` | `443` |
|
||||
| [`listen_addr_ipv4`](#listen_addr_ipv4) | `String` | `"0.0.0.0"` |
|
||||
| [`listen_addr_ipv6`](#listen_addr_ipv6) | `String` | `"::"` |
|
||||
| [`listen_unix_sock`](#listen_unix_sock) | `String` | — |
|
||||
| [`listen_unix_sock_perm`](#listen_unix_sock_perm) | `String` | — |
|
||||
| [`listen_tcp`](#listen_tcp) | `bool` | — (auto) |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | `false` |
|
||||
| [`proxy_protocol_header_timeout_ms`](#proxy_protocol_header_timeout_ms) | `u64` | `500` |
|
||||
| [`proxy_protocol_trusted_cidrs`](#proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` |
|
||||
| [`metrics_port`](#metrics_port) | `u16` | — |
|
||||
| [`metrics_listen`](#metrics_listen) | `String` | — |
|
||||
| [`metrics_whitelist`](#metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` |
|
||||
| [`max_connections`](#max_connections) | `u32` | `10000` |
|
||||
| [`accept_permit_timeout_ms`](#accept_permit_timeout_ms) | `u64` | `250` |
|
||||
| [`listen_backlog`](#listen_backlog) | `u32` | `1024` |
|
||||
|
||||
## port
|
||||
- **Constraints / validation**: `u16`.
|
||||
@@ -1973,16 +1930,16 @@ Note: When `server.proxy_protocol` is enabled, incoming PROXY protocol headers a
|
||||
Note: The conntrack-control worker runs **only on Linux**. On other operating systems it is not started; if `inline_conntrack_control` is `true`, a warning is logged. Effective operation also requires **CAP_NET_ADMIN** and a usable backend (`nft` or `iptables` / `ip6tables` on `PATH`). The `conntrack` utility is used for optional table entry deletes under pressure.
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`inline_conntrack_control`](#inline_conntrack_control) | `bool` | `true` | `✘` |
|
||||
| [`mode`](#mode) | `String` | `"tracked"` | `✘` |
|
||||
| [`backend`](#backend) | `String` | `"auto"` | `✘` |
|
||||
| [`profile`](#profile) | `String` | `"balanced"` | `✘` |
|
||||
| [`hybrid_listener_ips`](#hybrid_listener_ips) | `IpAddr[]` | `[]` | `✘` |
|
||||
| [`pressure_high_watermark_pct`](#pressure_high_watermark_pct) | `u8` | `85` | `✘` |
|
||||
| [`pressure_low_watermark_pct`](#pressure_low_watermark_pct) | `u8` | `70` | `✘` |
|
||||
| [`delete_budget_per_sec`](#delete_budget_per_sec) | `u64` | `4096` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`inline_conntrack_control`](#inline_conntrack_control) | `bool` | `true` |
|
||||
| [`mode`](#mode) | `String` | `"tracked"` |
|
||||
| [`backend`](#backend) | `String` | `"auto"` |
|
||||
| [`profile`](#profile) | `String` | `"balanced"` |
|
||||
| [`hybrid_listener_ips`](#hybrid_listener_ips) | `IpAddr[]` | `[]` |
|
||||
| [`pressure_high_watermark_pct`](#pressure_high_watermark_pct) | `u8` | `85` |
|
||||
| [`pressure_low_watermark_pct`](#pressure_low_watermark_pct) | `u8` | `70` |
|
||||
| [`delete_budget_per_sec`](#delete_budget_per_sec) | `u64` | `4096` |
|
||||
|
||||
## inline_conntrack_control
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -2064,21 +2021,21 @@ Note: The conntrack-control worker runs **only on Linux**. On other operating sy
|
||||
Note: This section also accepts the legacy alias `[server.admin_api]` (same schema as `[server.api]`).
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`enabled`](#enabled) | `bool` | `true` | `✘` |
|
||||
| [`listen`](#listen) | `String` | `"0.0.0.0:9091"` | `✘` |
|
||||
| [`whitelist`](#whitelist) | `IpNetwork[]` | `["127.0.0.0/8"]` | `✘` |
|
||||
| [`auth_header`](#auth_header) | `String` | `""` | `✘` |
|
||||
| [`request_body_limit_bytes`](#request_body_limit_bytes) | `usize` | `65536` | `✘` |
|
||||
| [`minimal_runtime_enabled`](#minimal_runtime_enabled) | `bool` | `true` | `✘` |
|
||||
| [`minimal_runtime_cache_ttl_ms`](#minimal_runtime_cache_ttl_ms) | `u64` | `1000` | `✘` |
|
||||
| [`runtime_edge_enabled`](#runtime_edge_enabled) | `bool` | `false` | `✘` |
|
||||
| [`runtime_edge_cache_ttl_ms`](#runtime_edge_cache_ttl_ms) | `u64` | `1000` | `✘` |
|
||||
| [`runtime_edge_top_n`](#runtime_edge_top_n) | `usize` | `10` | `✘` |
|
||||
| [`runtime_edge_events_capacity`](#runtime_edge_events_capacity) | `usize` | `256` | `✘` |
|
||||
| [`read_only`](#read_only) | `bool` | `false` | `✘` |
|
||||
| [`gray_action`](#gray_action) | `"drop"`, `"api"`, or `"200"` | `"drop"` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`enabled`](#enabled) | `bool` | `true` |
|
||||
| [`listen`](#listen) | `String` | `"0.0.0.0:9091"` |
|
||||
| [`whitelist`](#whitelist) | `IpNetwork[]` | `["127.0.0.0/8"]` |
|
||||
| [`auth_header`](#auth_header) | `String` | `""` |
|
||||
| [`request_body_limit_bytes`](#request_body_limit_bytes) | `usize` | `65536` |
|
||||
| [`minimal_runtime_enabled`](#minimal_runtime_enabled) | `bool` | `true` |
|
||||
| [`minimal_runtime_cache_ttl_ms`](#minimal_runtime_cache_ttl_ms) | `u64` | `1000` |
|
||||
| [`runtime_edge_enabled`](#runtime_edge_enabled) | `bool` | `false` |
|
||||
| [`runtime_edge_cache_ttl_ms`](#runtime_edge_cache_ttl_ms) | `u64` | `1000` |
|
||||
| [`runtime_edge_top_n`](#runtime_edge_top_n) | `usize` | `10` |
|
||||
| [`runtime_edge_events_capacity`](#runtime_edge_events_capacity) | `usize` | `256` |
|
||||
| [`read_only`](#read_only) | `bool` | `false` |
|
||||
| [`gray_action`](#gray_action) | `"drop"`, `"api"`, or `"200"` | `"drop"` |
|
||||
|
||||
## enabled
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -2202,14 +2159,13 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
# [[server.listeners]]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`ip`](#ip) | `IpAddr` | — | `✘` |
|
||||
| [`port`](#port-serverlisteners) | `u16` | `server.port` | `✘` |
|
||||
| [`announce`](#announce) | `String` | — | `✘` |
|
||||
| [`announce_ip`](#announce_ip) | `IpAddr` | — | `✘` |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | — | `✘` |
|
||||
| [`reuse_allow`](#reuse_allow) | `bool` | `false` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`ip`](#ip) | `IpAddr` | — |
|
||||
| [`announce`](#announce) | `String` | — |
|
||||
| [`announce_ip`](#announce_ip) | `IpAddr` | — |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | — |
|
||||
| [`reuse_allow`](#reuse_allow) | `bool` | `false` |
|
||||
|
||||
## ip
|
||||
- **Constraints / validation**: Required field. Must be an `IpAddr`.
|
||||
@@ -2220,16 +2176,6 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
[[server.listeners]]
|
||||
ip = "0.0.0.0"
|
||||
```
|
||||
## port (server.listeners)
|
||||
- **Constraints / validation**: `u16` (optional). When omitted, falls back to `server.port`.
|
||||
- **Description**: Per-listener TCP port.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[[server.listeners]]
|
||||
ip = "0.0.0.0"
|
||||
port = 443
|
||||
```
|
||||
## announce
|
||||
- **Constraints / validation**: `String` (optional). Must not be empty when set.
|
||||
- **Description**: Public IP/domain announced in proxy links for this listener. Takes precedence over `announce_ip`.
|
||||
@@ -2263,7 +2209,8 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
ip = "0.0.0.0"
|
||||
proxy_protocol = true
|
||||
```
|
||||
## reuse_allow
|
||||
## reuse_allow"
|
||||
- `reuse_allow`
|
||||
- **Constraints / validation**: `bool`.
|
||||
- **Description**: Enables `SO_REUSEPORT` for multi-instance bind sharing (allows multiple telemt instances to listen on the same `ip:port`).
|
||||
- **Example**:
|
||||
@@ -2278,18 +2225,18 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
# [timeouts]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`client_first_byte_idle_secs`](#client_first_byte_idle_secs) | `u64` | `300` | `✘` |
|
||||
| [`client_handshake`](#client_handshake) | `u64` | `30` | `✘` |
|
||||
| [`relay_idle_policy_v2_enabled`](#relay_idle_policy_v2_enabled) | `bool` | `true` | `✘` |
|
||||
| [`relay_client_idle_soft_secs`](#relay_client_idle_soft_secs) | `u64` | `120` | `✘` |
|
||||
| [`relay_client_idle_hard_secs`](#relay_client_idle_hard_secs) | `u64` | `360` | `✘` |
|
||||
| [`relay_idle_grace_after_downstream_activity_secs`](#relay_idle_grace_after_downstream_activity_secs) | `u64` | `30` | `✘` |
|
||||
| [`client_keepalive`](#client_keepalive) | `u64` | `15` | `✘` |
|
||||
| [`client_ack`](#client_ack) | `u64` | `90` | `✘` |
|
||||
| [`me_one_retry`](#me_one_retry) | `u8` | `12` | `✘` |
|
||||
| [`me_one_timeout_ms`](#me_one_timeout_ms) | `u64` | `1200` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`client_handshake`](#client_handshake) | `u64` | `30` |
|
||||
| [`relay_idle_policy_v2_enabled`](#relay_idle_policy_v2_enabled) | `bool` | `true` |
|
||||
| [`relay_client_idle_soft_secs`](#relay_client_idle_soft_secs) | `u64` | `120` |
|
||||
| [`relay_client_idle_hard_secs`](#relay_client_idle_hard_secs) | `u64` | `360` |
|
||||
| [`relay_idle_grace_after_downstream_activity_secs`](#relay_idle_grace_after_downstream_activity_secs) | `u64` | `30` |
|
||||
| [`tg_connect`](#tg_connect) | `u64` | `10` |
|
||||
| [`client_keepalive`](#client_keepalive) | `u64` | `15` |
|
||||
| [`client_ack`](#client_ack) | `u64` | `90` |
|
||||
| [`me_one_retry`](#me_one_retry) | `u8` | `12` |
|
||||
| [`me_one_timeout_ms`](#me_one_timeout_ms) | `u64` | `1200` |
|
||||
|
||||
## client_handshake
|
||||
- **Constraints / validation**: Must be `> 0`. Value is in seconds. Also used as an upper bound for some TLS emulation delays (see `censorship.server_hello_delay_max_ms`).
|
||||
@@ -2345,6 +2292,15 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
[timeouts]
|
||||
relay_idle_grace_after_downstream_activity_secs = 30
|
||||
```
|
||||
## tg_connect
|
||||
- **Constraints / validation**: `u64`. Value is in seconds.
|
||||
- **Description**: Upstream Telegram connect timeout (seconds).
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[timeouts]
|
||||
tg_connect = 10
|
||||
```
|
||||
## client_keepalive
|
||||
- **Constraints / validation**: `u64`. Value is in seconds.
|
||||
- **Description**: Client keepalive timeout (seconds).
|
||||
@@ -2386,40 +2342,40 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
# [censorship]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`tls_domain`](#tls_domain) | `String` | `"petrovich.ru"` | `✘` |
|
||||
| [`tls_domains`](#tls_domains) | `String[]` | `[]` | `✘` |
|
||||
| [`unknown_sni_action`](#unknown_sni_action) | `"drop"`, `"mask"`, `"accept"`, `"reject_handshake"` | `"drop"` | `✘` |
|
||||
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` |
|
||||
| [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults | `✘` |
|
||||
| [`mask`](#mask) | `bool` | `true` | `✘` |
|
||||
| [`mask_host`](#mask_host) | `String` | — | `✘` |
|
||||
| [`mask_port`](#mask_port) | `u16` | `443` | `✘` |
|
||||
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` |
|
||||
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` |
|
||||
| [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` |
|
||||
| [`tls_front_dir`](#tls_front_dir) | `String` | `"tlsfront"` | `✘` |
|
||||
| [`server_hello_delay_min_ms`](#server_hello_delay_min_ms) | `u64` | `0` | `✘` |
|
||||
| [`server_hello_delay_max_ms`](#server_hello_delay_max_ms) | `u64` | `0` | `✘` |
|
||||
| [`tls_new_session_tickets`](#tls_new_session_tickets) | `u8` | `0` | `✘` |
|
||||
| [`tls_full_cert_ttl_secs`](#tls_full_cert_ttl_secs) | `u64` | `90` | `✘` |
|
||||
| [`serverhello_compact`](#serverhello_compact) | `bool` | `false` | `✘` |
|
||||
| [`alpn_enforce`](#alpn_enforce) | `bool` | `true` | `✘` |
|
||||
| [`mask_proxy_protocol`](#mask_proxy_protocol) | `u8` | `0` | `✘` |
|
||||
| [`mask_shape_hardening`](#mask_shape_hardening) | `bool` | `true` | `✘` |
|
||||
| [`mask_shape_hardening_aggressive_mode`](#mask_shape_hardening_aggressive_mode) | `bool` | `false` | `✘` |
|
||||
| [`mask_shape_bucket_floor_bytes`](#mask_shape_bucket_floor_bytes) | `usize` | `512` | `✘` |
|
||||
| [`mask_shape_bucket_cap_bytes`](#mask_shape_bucket_cap_bytes) | `usize` | `4096` | `✘` |
|
||||
| [`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_relay_max_bytes`](#mask_relay_max_bytes) | `usize` | `5242880` | `✘` |
|
||||
| [`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_classifier_prefetch_timeout_ms`](#mask_classifier_prefetch_timeout_ms) | `u64` | `5` | `✘` |
|
||||
| [`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_ceiling_ms`](#mask_timing_normalization_ceiling_ms) | `u64` | `0` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`tls_domain`](#tls_domain) | `String` | `"petrovich.ru"` |
|
||||
| [`tls_domains`](#tls_domains) | `String[]` | `[]` |
|
||||
| [`unknown_sni_action`](#unknown_sni_action) | `"drop"`, `"mask"`, `"accept"`, `"reject_handshake"` | `"drop"` |
|
||||
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` |
|
||||
| [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults |
|
||||
| [`mask`](#mask) | `bool` | `true` |
|
||||
| [`mask_host`](#mask_host) | `String` | — |
|
||||
| [`mask_port`](#mask_port) | `u16` | `443` |
|
||||
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — |
|
||||
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` |
|
||||
| [`tls_emulation`](#tls_emulation) | `bool` | `true` |
|
||||
| [`tls_front_dir`](#tls_front_dir) | `String` | `"tlsfront"` |
|
||||
| [`server_hello_delay_min_ms`](#server_hello_delay_min_ms) | `u64` | `0` |
|
||||
| [`server_hello_delay_max_ms`](#server_hello_delay_max_ms) | `u64` | `0` |
|
||||
| [`tls_new_session_tickets`](#tls_new_session_tickets) | `u8` | `0` |
|
||||
| [`tls_full_cert_ttl_secs`](#tls_full_cert_ttl_secs) | `u64` | `90` |
|
||||
| [`serverhello_compact`](#serverhello_compact) | `bool` | `false` |
|
||||
| [`alpn_enforce`](#alpn_enforce) | `bool` | `true` |
|
||||
| [`mask_proxy_protocol`](#mask_proxy_protocol) | `u8` | `0` |
|
||||
| [`mask_shape_hardening`](#mask_shape_hardening) | `bool` | `true` |
|
||||
| [`mask_shape_hardening_aggressive_mode`](#mask_shape_hardening_aggressive_mode) | `bool` | `false` |
|
||||
| [`mask_shape_bucket_floor_bytes`](#mask_shape_bucket_floor_bytes) | `usize` | `512` |
|
||||
| [`mask_shape_bucket_cap_bytes`](#mask_shape_bucket_cap_bytes) | `usize` | `4096` |
|
||||
| [`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_relay_max_bytes`](#mask_relay_max_bytes) | `usize` | `5242880` |
|
||||
| [`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_classifier_prefetch_timeout_ms`](#mask_classifier_prefetch_timeout_ms) | `u64` | `5` |
|
||||
| [`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_ceiling_ms`](#mask_timing_normalization_ceiling_ms) | `u64` | `0` |
|
||||
|
||||
## tls_domain
|
||||
- **Constraints / validation**: Must be a non-empty domain name. Must not contain spaces or `/`.
|
||||
@@ -2841,15 +2797,15 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
# [censorship.tls_fetch]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`profiles`](#profiles) | `String[]` | `["modern_chrome_like", "modern_firefox_like", "compat_tls12", "legacy_minimal"]` | `✘` |
|
||||
| [`strict_route`](#strict_route) | `bool` | `true` | `✘` |
|
||||
| [`attempt_timeout_ms`](#attempt_timeout_ms) | `u64` | `5000` | `✘` |
|
||||
| [`total_budget_ms`](#total_budget_ms) | `u64` | `15000` | `✘` |
|
||||
| [`grease_enabled`](#grease_enabled) | `bool` | `false` | `✘` |
|
||||
| [`deterministic`](#deterministic) | `bool` | `false` | `✘` |
|
||||
| [`profile_cache_ttl_secs`](#profile_cache_ttl_secs) | `u64` | `600` | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`profiles`](#profiles) | `String[]` | `["modern_chrome_like", "modern_firefox_like", "compat_tls12", "legacy_minimal"]` |
|
||||
| [`strict_route`](#strict_route) | `bool` | `true` |
|
||||
| [`attempt_timeout_ms`](#attempt_timeout_ms) | `u64` | `5000` |
|
||||
| [`total_budget_ms`](#total_budget_ms) | `u64` | `15000` |
|
||||
| [`grease_enabled`](#grease_enabled) | `bool` | `false` |
|
||||
| [`deterministic`](#deterministic) | `bool` | `false` |
|
||||
| [`profile_cache_ttl_secs`](#profile_cache_ttl_secs) | `u64` | `600` |
|
||||
|
||||
## profiles
|
||||
- **Constraints / validation**: `String[]`. Empty list falls back to defaults; values are deduplicated preserving order.
|
||||
@@ -2918,24 +2874,24 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
# [access]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`users`](#users) | `Map<String, String>` | `{"default": "000…000"}` | `✔` |
|
||||
| [`user_ad_tags`](#user_ad_tags) | `Map<String, String>` | `{}` | `✔` |
|
||||
| [`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_expirations`](#user_expirations) | `Map<String, DateTime<Utc>>` | `{}` | `✔` |
|
||||
| [`user_data_quota`](#user_data_quota) | `Map<String, u64>` | `{}` | `✔` |
|
||||
| [`user_max_unique_ips`](#user_max_unique_ips) | `Map<String, usize>` | `{}` | `✔` |
|
||||
| [`user_max_unique_ips_global_each`](#user_max_unique_ips_global_each) | `usize` | `0` | `✔` |
|
||||
| [`user_max_unique_ips_mode`](#user_max_unique_ips_mode) | `"active_window"`, `"time_window"`, or `"combined"` | `"active_window"` | `✔` |
|
||||
| [`user_max_unique_ips_window_secs`](#user_max_unique_ips_window_secs) | `u64` | `30` | `✔` |
|
||||
| [`user_source_deny`](#user_source_deny) | `Map<String, IpNetwork[]>` | `{}` | `✘` |
|
||||
| [`replay_check_len`](#replay_check_len) | `usize` | `65536` | `✘` |
|
||||
| [`replay_window_secs`](#replay_window_secs) | `u64` | `120` | `✘` |
|
||||
| [`ignore_time_skew`](#ignore_time_skew) | `bool` | `false` | `✘` |
|
||||
| [`user_rate_limits`](#user_rate_limits) | `Map<String, RateLimitBps>` | `{}` | `✔` |
|
||||
| [`cidr_rate_limits`](#cidr_rate_limits) | `Map<IpNetwork, RateLimitBps>` | `{}` | `✔` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`users`](#users) | `Map<String, String>` | `{"default": "000…000"}` |
|
||||
| [`user_ad_tags`](#user_ad_tags) | `Map<String, String>` | `{}` |
|
||||
| [`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_expirations`](#user_expirations) | `Map<String, DateTime<Utc>>` | `{}` |
|
||||
| [`user_data_quota`](#user_data_quota) | `Map<String, u64>` | `{}` |
|
||||
| [`user_max_unique_ips`](#user_max_unique_ips) | `Map<String, usize>` | `{}` |
|
||||
| [`user_max_unique_ips_global_each`](#user_max_unique_ips_global_each) | `usize` | `0` |
|
||||
| [`user_max_unique_ips_mode`](#user_max_unique_ips_mode) | `"active_window"`, `"time_window"`, or `"combined"` | `"active_window"` |
|
||||
| [`user_max_unique_ips_window_secs`](#user_max_unique_ips_window_secs) | `u64` | `30` |
|
||||
| [`user_source_deny`](#user_source_deny) | `Map<String, IpNetwork[]>` | `{}` |
|
||||
| [`replay_check_len`](#replay_check_len) | `usize` | `65536` |
|
||||
| [`replay_window_secs`](#replay_window_secs) | `u64` | `120` |
|
||||
| [`ignore_time_skew`](#ignore_time_skew) | `bool` | `false` |
|
||||
| [`user_rate_limits`](#user_rate_limits) | `Map<String, RateLimitBps>` | `{}` |
|
||||
| [`cidr_rate_limits`](#cidr_rate_limits) | `Map<IpNetwork, RateLimitBps>` | `{}` |
|
||||
|
||||
## users
|
||||
- **Constraints / validation**: Must not be empty (at least one user must exist). Each value must be **exactly 32 hex characters**.
|
||||
@@ -3099,23 +3055,19 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
# [[upstreams]]
|
||||
|
||||
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`type`](#type) | `"direct"`, `"socks4"`, `"socks5"`, or `"shadowsocks"` | — | `✘` |
|
||||
| [`weight`](#weight) | `u16` | `1` | `✘` |
|
||||
| [`enabled`](#enabled) | `bool` | `true` | `✘` |
|
||||
| [`scopes`](#scopes) | `String` | `""` | `✘` |
|
||||
| [`ipv4`](#ipv4-upstreams) | `bool` | — (auto) | `✘` |
|
||||
| [`ipv6`](#ipv6-upstreams) | `bool` | — (auto) | `✘` |
|
||||
| [`interface`](#interface) | `String` | — | `✘` |
|
||||
| [`bind_addresses`](#bind_addresses) | `String[]` | — | `✘` |
|
||||
| [`bindtodevice`](#bindtodevice) | `String` | — | `✘` |
|
||||
| [`force_bind`](#force_bind) | `String` | — | `✘` |
|
||||
| [`url`](#url) | `String` | — | `✘` |
|
||||
| [`address`](#address) | `String` | — | `✘` |
|
||||
| [`user_id`](#user_id) | `String` | — | `✘` |
|
||||
| [`username`](#username) | `String` | — | `✘` |
|
||||
| [`password`](#password) | `String` | — | `✘` |
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`type`](#type) | `"direct"`, `"socks4"`, `"socks5"`, or `"shadowsocks"` | — |
|
||||
| [`weight`](#weight) | `u16` | `1` |
|
||||
| [`enabled`](#enabled) | `bool` | `true` |
|
||||
| [`scopes`](#scopes) | `String` | `""` |
|
||||
| [`interface`](#interface) | `String` | — |
|
||||
| [`bind_addresses`](#bind_addresses) | `String[]` | — |
|
||||
| [`url`](#url) | `String` | — |
|
||||
| [`address`](#address) | `String` | — |
|
||||
| [`user_id`](#user_id) | `String` | — |
|
||||
| [`username`](#username) | `String` | — |
|
||||
| [`password`](#password) | `String` | — |
|
||||
|
||||
## type
|
||||
- **Constraints / validation**: Required field. Must be one of: `"direct"`, `"socks4"`, `"socks5"`, `"shadowsocks"`.
|
||||
@@ -3166,26 +3118,6 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
address = "10.0.0.10:1080"
|
||||
scopes = "me, fetch, dc2"
|
||||
```
|
||||
## ipv4 (upstreams)
|
||||
- **Constraints / validation**: `bool` (optional).
|
||||
- **Description**: Allows IPv4 DC targets for this upstream. When omitted, Telemt auto-detects support from runtime connectivity state.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
ipv4 = true
|
||||
```
|
||||
## ipv6 (upstreams)
|
||||
- **Constraints / validation**: `bool` (optional).
|
||||
- **Description**: Allows IPv6 DC targets for this upstream. When omitted, Telemt auto-detects support from runtime connectivity state.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
ipv6 = false
|
||||
```
|
||||
## interface
|
||||
- **Constraints / validation**: `String` (optional).
|
||||
- For `"direct"`: may be an IP address (used as explicit local bind) or an OS interface name (resolved to an IP at runtime; Unix only).
|
||||
@@ -3216,26 +3148,6 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
type = "direct"
|
||||
bind_addresses = ["192.0.2.10", "192.0.2.11"]
|
||||
```
|
||||
## bindtodevice
|
||||
- **Constraints / validation**: `String` (optional). Applies only to `type = "direct"` and is Linux-only.
|
||||
- **Description**: Hard interface pinning via `SO_BINDTODEVICE` for outgoing direct TCP connects.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
bindtodevice = "eth0"
|
||||
```
|
||||
## force_bind
|
||||
- **Constraints / validation**: `String` (optional). Alias for `bindtodevice`.
|
||||
- **Description**: Backward-compatible alias for Linux `SO_BINDTODEVICE` hard interface pinning.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
force_bind = "eth0"
|
||||
```
|
||||
## url
|
||||
- **Constraints / validation**: Applies only to `type = "shadowsocks"`.
|
||||
- Must be a valid Shadowsocks URL accepted by the `shadowsocks` crate.
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
>
|
||||
> Параметры конфигурации, подробно описанные в этом документе, предназначены для опытных пользователей и для целей тонкой настройки. Изменение этих параметров без четкого понимания их функции может привести к нестабильности приложения или другому неожиданному поведению. Пожалуйста, действуйте осторожно и на свой страх и риск.
|
||||
|
||||
> `Hot-Reload` показывает, применяет ли config watcher изменение без перезапуска процесса; `✘` означает, что для runtime-эффекта нужен перезапуск.
|
||||
|
||||
# Содержание
|
||||
- [Ключи верхнего уровня](#top-level-keys)
|
||||
- [general](#general)
|
||||
@@ -31,16 +29,12 @@
|
||||
|
||||
# Ключи верхнего уровня
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`include`](#include) | `String` (специальная директива) | — | `✔` |
|
||||
| [`show_link`](#show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) | `✘` |
|
||||
| [`dc_overrides`](#dc_overrides) | `Map<String, String or String[]>` | `{}` | `✘` |
|
||||
| [`default_dc`](#default_dc) | `u8` | — (эффективный резервный вариант: `2` в ME маршрутизации) | `✘` |
|
||||
| [`beobachten`](#beobachten) | `bool` | `true` | `✘` |
|
||||
| [`beobachten_minutes`](#beobachten_minutes) | `u64` | `10` | `✘` |
|
||||
| [`beobachten_flush_secs`](#beobachten_flush_secs) | `u64` | `15` | `✘` |
|
||||
| [`beobachten_file`](#beobachten_file) | `String` | `"cache/beobachten.txt"` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`include`](#include) | `String` (специальная директива) | — |
|
||||
| [`show_link`](#show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) |
|
||||
| [`dc_overrides`](#dc_overrides) | `Map<String, String or String[]>` | `{}` |
|
||||
| [`default_dc`](#default_dc) | `u8` | — (эффективный резервный вариант: `2` в ME маршрутизации) |
|
||||
|
||||
## include
|
||||
- **Ограничения / валидация**: значение должно быть одной строкой в виде `include = "path/to/file.toml"`. Значения параметра обрабатываются перед анализом TOML. Максимальное количество - 10.
|
||||
@@ -85,151 +79,145 @@
|
||||
|
||||
# [general]
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`data_path`](#data_path) | `String` | — | `✘` |
|
||||
| [`quota_state_path`](#quota_state_path) | `Path` | `"telemt.limit.json"` | `✘` |
|
||||
| [`config_strict`](#config_strict) | `bool` | `false` | `✘` |
|
||||
| [`prefer_ipv6`](#prefer_ipv6) | `bool` | `false` | `✘` |
|
||||
| [`fast_mode`](#fast_mode) | `bool` | `true` | `✘` |
|
||||
| [`use_middle_proxy`](#use_middle_proxy) | `bool` | `true` | `✘` |
|
||||
| [`proxy_secret_path`](#proxy_secret_path) | `String` | `"proxy-secret"` | `✘` |
|
||||
| [`proxy_secret_url`](#proxy_secret_url) | `String` | `"https://core.telegram.org/getProxySecret"` | `✘` |
|
||||
| [`proxy_config_v4_cache_path`](#proxy_config_v4_cache_path) | `String` | `"cache/proxy-config-v4.txt"` | `✘` |
|
||||
| [`proxy_config_v4_url`](#proxy_config_v4_url) | `String` | `"https://core.telegram.org/getProxyConfig"` | `✘` |
|
||||
| [`proxy_config_v6_cache_path`](#proxy_config_v6_cache_path) | `String` | `"cache/proxy-config-v6.txt"` | `✘` |
|
||||
| [`proxy_config_v6_url`](#proxy_config_v6_url) | `String` | `"https://core.telegram.org/getProxyConfigV6"` | `✘` |
|
||||
| [`ad_tag`](#ad_tag) | `String` | — | `✔` |
|
||||
| [`middle_proxy_nat_ip`](#middle_proxy_nat_ip) | `IpAddr` | — | `✘` |
|
||||
| [`middle_proxy_nat_probe`](#middle_proxy_nat_probe) | `bool` | `true` | `✘` |
|
||||
| [`middle_proxy_nat_stun`](#middle_proxy_nat_stun) | `String` | — | `✘` |
|
||||
| [`middle_proxy_nat_stun_servers`](#middle_proxy_nat_stun_servers) | `String[]` | `[]` | `✘` |
|
||||
| [`stun_nat_probe_concurrency`](#stun_nat_probe_concurrency) | `usize` | `8` | `✘` |
|
||||
| [`middle_proxy_pool_size`](#middle_proxy_pool_size) | `usize` | `8` | `✘` |
|
||||
| [`middle_proxy_warm_standby`](#middle_proxy_warm_standby) | `usize` | `16` | `✘` |
|
||||
| [`me_init_retry_attempts`](#me_init_retry_attempts) | `u32` | `0` | `✘` |
|
||||
| [`me2dc_fallback`](#me2dc_fallback) | `bool` | `true` | `✘` |
|
||||
| [`me2dc_fast`](#me2dc_fast) | `bool` | `false` | `✘` |
|
||||
| [`me_keepalive_enabled`](#me_keepalive_enabled) | `bool` | `true` | `✘` |
|
||||
| [`me_keepalive_interval_secs`](#me_keepalive_interval_secs) | `u64` | `8` | `✘` |
|
||||
| [`me_keepalive_jitter_secs`](#me_keepalive_jitter_secs) | `u64` | `2` | `✘` |
|
||||
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` | `✘` |
|
||||
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` | `✘` |
|
||||
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` | `✘` |
|
||||
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` | `✘` |
|
||||
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` | `✘` |
|
||||
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` | `✘` |
|
||||
| [`me_reader_route_data_wait_ms`](#me_reader_route_data_wait_ms) | `u64` | `2` | `✔` |
|
||||
| [`me_d2c_flush_batch_max_frames`](#me_d2c_flush_batch_max_frames) | `usize` | `32` | `✔` |
|
||||
| [`me_d2c_flush_batch_max_bytes`](#me_d2c_flush_batch_max_bytes) | `usize` | `131072` | `✔` |
|
||||
| [`me_d2c_flush_batch_max_delay_us`](#me_d2c_flush_batch_max_delay_us) | `u64` | `500` | `✔` |
|
||||
| [`me_d2c_ack_flush_immediate`](#me_d2c_ack_flush_immediate) | `bool` | `true` | `✔` |
|
||||
| [`me_quota_soft_overshoot_bytes`](#me_quota_soft_overshoot_bytes) | `u64` | `65536` | `✔` |
|
||||
| [`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_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` | `✔` |
|
||||
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` | `✘` |
|
||||
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` | `✘` |
|
||||
| [`desync_all_full`](#desync_all_full) | `bool` | `false` | `✔` |
|
||||
| [`beobachten`](#beobachten) | `bool` | `true` | `✘` |
|
||||
| [`beobachten_minutes`](#beobachten_minutes) | `u64` | `10` | `✘` |
|
||||
| [`beobachten_flush_secs`](#beobachten_flush_secs) | `u64` | `15` | `✘` |
|
||||
| [`beobachten_file`](#beobachten_file) | `String` | `"cache/beobachten.txt"` | `✘` |
|
||||
| [`hardswap`](#hardswap) | `bool` | `true` | `✔` |
|
||||
| [`me_warmup_stagger_enabled`](#me_warmup_stagger_enabled) | `bool` | `true` | `✘` |
|
||||
| [`me_warmup_step_delay_ms`](#me_warmup_step_delay_ms) | `u64` | `500` | `✘` |
|
||||
| [`me_warmup_step_jitter_ms`](#me_warmup_step_jitter_ms) | `u64` | `300` | `✘` |
|
||||
| [`me_reconnect_max_concurrent_per_dc`](#me_reconnect_max_concurrent_per_dc) | `u32` | `8` | `✘` |
|
||||
| [`me_reconnect_backoff_base_ms`](#me_reconnect_backoff_base_ms) | `u64` | `500` | `✘` |
|
||||
| [`me_reconnect_backoff_cap_ms`](#me_reconnect_backoff_cap_ms) | `u64` | `30000` | `✘` |
|
||||
| [`me_reconnect_fast_retry_count`](#me_reconnect_fast_retry_count) | `u32` | `16` | `✘` |
|
||||
| [`me_single_endpoint_shadow_writers`](#me_single_endpoint_shadow_writers) | `u8` | `2` | `✔` |
|
||||
| [`me_single_endpoint_outage_mode_enabled`](#me_single_endpoint_outage_mode_enabled) | `bool` | `true` | `✔` |
|
||||
| [`me_single_endpoint_outage_disable_quarantine`](#me_single_endpoint_outage_disable_quarantine) | `bool` | `true` | `✔` |
|
||||
| [`me_single_endpoint_outage_backoff_min_ms`](#me_single_endpoint_outage_backoff_min_ms) | `u64` | `250` | `✔` |
|
||||
| [`me_single_endpoint_outage_backoff_max_ms`](#me_single_endpoint_outage_backoff_max_ms) | `u64` | `3000` | `✔` |
|
||||
| [`me_single_endpoint_shadow_rotate_every_secs`](#me_single_endpoint_shadow_rotate_every_secs) | `u64` | `900` | `✔` |
|
||||
| [`me_floor_mode`](#me_floor_mode) | `"static"` or `"adaptive"` | `"adaptive"` | `✔` |
|
||||
| [`me_adaptive_floor_idle_secs`](#me_adaptive_floor_idle_secs) | `u64` | `90` | `✔` |
|
||||
| [`me_adaptive_floor_min_writers_single_endpoint`](#me_adaptive_floor_min_writers_single_endpoint) | `u8` | `1` | `✔` |
|
||||
| [`me_adaptive_floor_min_writers_multi_endpoint`](#me_adaptive_floor_min_writers_multi_endpoint) | `u8` | `1` | `✔` |
|
||||
| [`me_adaptive_floor_recover_grace_secs`](#me_adaptive_floor_recover_grace_secs) | `u64` | `180` | `✔` |
|
||||
| [`me_adaptive_floor_writers_per_core_total`](#me_adaptive_floor_writers_per_core_total) | `u16` | `48` | `✔` |
|
||||
| [`me_adaptive_floor_cpu_cores_override`](#me_adaptive_floor_cpu_cores_override) | `u16` | `0` | `✔` |
|
||||
| [`me_adaptive_floor_max_extra_writers_single_per_core`](#me_adaptive_floor_max_extra_writers_single_per_core) | `u16` | `1` | `✔` |
|
||||
| [`me_adaptive_floor_max_extra_writers_multi_per_core`](#me_adaptive_floor_max_extra_writers_multi_per_core) | `u16` | `2` | `✔` |
|
||||
| [`me_adaptive_floor_max_active_writers_per_core`](#me_adaptive_floor_max_active_writers_per_core) | `u16` | `64` | `✔` |
|
||||
| [`me_adaptive_floor_max_warm_writers_per_core`](#me_adaptive_floor_max_warm_writers_per_core) | `u16` | `64` | `✔` |
|
||||
| [`me_adaptive_floor_max_active_writers_global`](#me_adaptive_floor_max_active_writers_global) | `u32` | `256` | `✔` |
|
||||
| [`me_adaptive_floor_max_warm_writers_global`](#me_adaptive_floor_max_warm_writers_global) | `u32` | `256` | `✔` |
|
||||
| [`upstream_connect_retry_attempts`](#upstream_connect_retry_attempts) | `u32` | `2` | `✘` |
|
||||
| [`upstream_connect_retry_backoff_ms`](#upstream_connect_retry_backoff_ms) | `u64` | `100` | `✘` |
|
||||
| [`upstream_connect_budget_ms`](#upstream_connect_budget_ms) | `u64` | `3000` | `✘` |
|
||||
| [`tg_connect`](#tg_connect) | `u64` | `10` | `✘` |
|
||||
| [`upstream_unhealthy_fail_threshold`](#upstream_unhealthy_fail_threshold) | `u32` | `5` | `✘` |
|
||||
| [`upstream_connect_failfast_hard_errors`](#upstream_connect_failfast_hard_errors) | `bool` | `false` | `✘` |
|
||||
| [`stun_iface_mismatch_ignore`](#stun_iface_mismatch_ignore) | `bool` | `false` | `✘` |
|
||||
| [`unknown_dc_log_path`](#unknown_dc_log_path) | `String` | `"unknown-dc.txt"` | `✘` |
|
||||
| [`unknown_dc_file_log_enabled`](#unknown_dc_file_log_enabled) | `bool` | `false` | `✘` |
|
||||
| [`log_level`](#log_level) | `"debug"`, `"verbose"`, `"normal"`, or `"silent"` | `"normal"` | `✔` |
|
||||
| [`disable_colors`](#disable_colors) | `bool` | `false` | `✘` |
|
||||
| [`me_socks_kdf_policy`](#me_socks_kdf_policy) | `"strict"` or `"compat"` | `"strict"` | `✔` |
|
||||
| [`me_route_backpressure_enabled`](#me_route_backpressure_enabled) | `bool` | `false` | `✔` |
|
||||
| [`me_route_fairshare_enabled`](#me_route_fairshare_enabled) | `bool` | `false` | `✔` |
|
||||
| [`me_route_backpressure_base_timeout_ms`](#me_route_backpressure_base_timeout_ms) | `u64` | `25` | `✔` |
|
||||
| [`me_route_backpressure_high_timeout_ms`](#me_route_backpressure_high_timeout_ms) | `u64` | `120` | `✔` |
|
||||
| [`me_route_backpressure_high_watermark_pct`](#me_route_backpressure_high_watermark_pct) | `u8` | `80` | `✔` |
|
||||
| [`me_health_interval_ms_unhealthy`](#me_health_interval_ms_unhealthy) | `u64` | `1000` | `✔` |
|
||||
| [`me_health_interval_ms_healthy`](#me_health_interval_ms_healthy) | `u64` | `3000` | `✔` |
|
||||
| [`me_admission_poll_ms`](#me_admission_poll_ms) | `u64` | `1000` | `✔` |
|
||||
| [`me_warn_rate_limit_ms`](#me_warn_rate_limit_ms) | `u64` | `5000` | `✔` |
|
||||
| [`me_route_no_writer_mode`](#me_route_no_writer_mode) | `"async_recovery_failfast"`, `"inline_recovery_legacy"`, or `"hybrid_async_persistent"` | `"hybrid_async_persistent"` | `✘` |
|
||||
| [`me_route_no_writer_wait_ms`](#me_route_no_writer_wait_ms) | `u64` | `250` | `✘` |
|
||||
| [`me_route_hybrid_max_wait_ms`](#me_route_hybrid_max_wait_ms) | `u64` | `3000` | `✘` |
|
||||
| [`me_route_blocking_send_timeout_ms`](#me_route_blocking_send_timeout_ms) | `u64` | `250` | `✘` |
|
||||
| [`me_route_inline_recovery_attempts`](#me_route_inline_recovery_attempts) | `u32` | `3` | `✘` |
|
||||
| [`me_route_inline_recovery_wait_ms`](#me_route_inline_recovery_wait_ms) | `u64` | `3000` | `✘` |
|
||||
| [`fast_mode_min_tls_record`](#fast_mode_min_tls_record) | `usize` | `0` | `✘` |
|
||||
| [`update_every`](#update_every) | `u64` | `300` | `✔` |
|
||||
| [`me_reinit_every_secs`](#me_reinit_every_secs) | `u64` | `900` | `✔` |
|
||||
| [`me_hardswap_warmup_delay_min_ms`](#me_hardswap_warmup_delay_min_ms) | `u64` | `1000` | `✔` |
|
||||
| [`me_hardswap_warmup_delay_max_ms`](#me_hardswap_warmup_delay_max_ms) | `u64` | `2000` | `✔` |
|
||||
| [`me_hardswap_warmup_extra_passes`](#me_hardswap_warmup_extra_passes) | `u8` | `3` | `✔` |
|
||||
| [`me_hardswap_warmup_pass_backoff_base_ms`](#me_hardswap_warmup_pass_backoff_base_ms) | `u64` | `500` | `✔` |
|
||||
| [`me_config_stable_snapshots`](#me_config_stable_snapshots) | `u8` | `2` | `✔` |
|
||||
| [`me_config_apply_cooldown_secs`](#me_config_apply_cooldown_secs) | `u64` | `300` | `✔` |
|
||||
| [`me_snapshot_require_http_2xx`](#me_snapshot_require_http_2xx) | `bool` | `true` | `✔` |
|
||||
| [`me_snapshot_reject_empty_map`](#me_snapshot_reject_empty_map) | `bool` | `true` | `✔` |
|
||||
| [`me_snapshot_min_proxy_for_lines`](#me_snapshot_min_proxy_for_lines) | `u32` | `1` | `✔` |
|
||||
| [`proxy_secret_stable_snapshots`](#proxy_secret_stable_snapshots) | `u8` | `2` | `✔` |
|
||||
| [`proxy_secret_rotate_runtime`](#proxy_secret_rotate_runtime) | `bool` | `true` | `✔` |
|
||||
| [`me_secret_atomic_snapshot`](#me_secret_atomic_snapshot) | `bool` | `true` | `✔` |
|
||||
| [`proxy_secret_len_max`](#proxy_secret_len_max) | `usize` | `256` | `✔` |
|
||||
| [`me_pool_drain_ttl_secs`](#me_pool_drain_ttl_secs) | `u64` | `90` | `✔` |
|
||||
| [`me_instadrain`](#me_instadrain) | `bool` | `false` | `✔` |
|
||||
| [`me_pool_drain_threshold`](#me_pool_drain_threshold) | `u64` | `32` | `✔` |
|
||||
| [`me_pool_drain_soft_evict_enabled`](#me_pool_drain_soft_evict_enabled) | `bool` | `true` | `✘` |
|
||||
| [`me_pool_drain_soft_evict_grace_secs`](#me_pool_drain_soft_evict_grace_secs) | `u64` | `10` | `✘` |
|
||||
| [`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_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_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` | `✔` |
|
||||
| [`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` | `✔` |
|
||||
| [`proxy_secret_auto_reload_secs`](#proxy_secret_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_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` | `✘` |
|
||||
| [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` | `✔` |
|
||||
| [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` | `✔` |
|
||||
| [`me_writer_pick_mode`](#me_writer_pick_mode) | `"sorted_rr"` or `"p2c"` | `"p2c"` | `✔` |
|
||||
| [`me_writer_pick_sample_size`](#me_writer_pick_sample_size) | `u8` | `3` | `✔` |
|
||||
| [`ntp_check`](#ntp_check) | `bool` | `true` | `✘` |
|
||||
| [`ntp_servers`](#ntp_servers) | `String[]` | `["pool.ntp.org"]` | `✘` |
|
||||
| [`auto_degradation_enabled`](#auto_degradation_enabled) | `bool` | `true` | `✘` |
|
||||
| [`degradation_min_unavailable_dc_groups`](#degradation_min_unavailable_dc_groups) | `u8` | `2` | `✘` |
|
||||
| [`rst_on_close`](#rst_on_close) | `"off"`, `"errors"` или `"always"` | `"off"` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`data_path`](#data_path) | `String` | — |
|
||||
| [`prefer_ipv6`](#prefer_ipv6) | `bool` | `false` |
|
||||
| [`fast_mode`](#fast_mode) | `bool` | `true` |
|
||||
| [`use_middle_proxy`](#use_middle_proxy) | `bool` | `true` |
|
||||
| [`proxy_secret_path`](#proxy_secret_path) | `String` | `"proxy-secret"` |
|
||||
| [`proxy_config_v4_cache_path`](#proxy_config_v4_cache_path) | `String` | `"cache/proxy-config-v4.txt"` |
|
||||
| [`proxy_config_v6_cache_path`](#proxy_config_v6_cache_path) | `String` | `"cache/proxy-config-v6.txt"` |
|
||||
| [`ad_tag`](#ad_tag) | `String` | — |
|
||||
| [`middle_proxy_nat_ip`](#middle_proxy_nat_ip) | `IpAddr` | — |
|
||||
| [`middle_proxy_nat_probe`](#middle_proxy_nat_probe) | `bool` | `true` |
|
||||
| [`middle_proxy_nat_stun`](#middle_proxy_nat_stun) | `String` | — |
|
||||
| [`middle_proxy_nat_stun_servers`](#middle_proxy_nat_stun_servers) | `String[]` | `[]` |
|
||||
| [`stun_nat_probe_concurrency`](#stun_nat_probe_concurrency) | `usize` | `8` |
|
||||
| [`middle_proxy_pool_size`](#middle_proxy_pool_size) | `usize` | `8` |
|
||||
| [`middle_proxy_warm_standby`](#middle_proxy_warm_standby) | `usize` | `16` |
|
||||
| [`me_init_retry_attempts`](#me_init_retry_attempts) | `u32` | `0` |
|
||||
| [`me2dc_fallback`](#me2dc_fallback) | `bool` | `true` |
|
||||
| [`me2dc_fast`](#me2dc_fast) | `bool` | `false` |
|
||||
| [`me_keepalive_enabled`](#me_keepalive_enabled) | `bool` | `true` |
|
||||
| [`me_keepalive_interval_secs`](#me_keepalive_interval_secs) | `u64` | `8` |
|
||||
| [`me_keepalive_jitter_secs`](#me_keepalive_jitter_secs) | `u64` | `2` |
|
||||
| [`me_keepalive_payload_random`](#me_keepalive_payload_random) | `bool` | `true` |
|
||||
| [`rpc_proxy_req_every`](#rpc_proxy_req_every) | `u64` | `0` |
|
||||
| [`me_writer_cmd_channel_capacity`](#me_writer_cmd_channel_capacity) | `usize` | `4096` |
|
||||
| [`me_route_channel_capacity`](#me_route_channel_capacity) | `usize` | `768` |
|
||||
| [`me_c2me_channel_capacity`](#me_c2me_channel_capacity) | `usize` | `1024` |
|
||||
| [`me_c2me_send_timeout_ms`](#me_c2me_send_timeout_ms) | `u64` | `4000` |
|
||||
| [`me_reader_route_data_wait_ms`](#me_reader_route_data_wait_ms) | `u64` | `2` |
|
||||
| [`me_d2c_flush_batch_max_frames`](#me_d2c_flush_batch_max_frames) | `usize` | `32` |
|
||||
| [`me_d2c_flush_batch_max_bytes`](#me_d2c_flush_batch_max_bytes) | `usize` | `131072` |
|
||||
| [`me_d2c_flush_batch_max_delay_us`](#me_d2c_flush_batch_max_delay_us) | `u64` | `500` |
|
||||
| [`me_d2c_ack_flush_immediate`](#me_d2c_ack_flush_immediate) | `bool` | `true` |
|
||||
| [`me_quota_soft_overshoot_bytes`](#me_quota_soft_overshoot_bytes) | `u64` | `65536` |
|
||||
| [`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_s2c_bytes`](#direct_relay_copy_buf_s2c_bytes) | `usize` | `262144` |
|
||||
| [`crypto_pending_buffer`](#crypto_pending_buffer) | `usize` | `262144` |
|
||||
| [`max_client_frame`](#max_client_frame) | `usize` | `16777216` |
|
||||
| [`desync_all_full`](#desync_all_full) | `bool` | `false` |
|
||||
| [`beobachten`](#beobachten) | `bool` | `true` |
|
||||
| [`beobachten_minutes`](#beobachten_minutes) | `u64` | `10` |
|
||||
| [`beobachten_flush_secs`](#beobachten_flush_secs) | `u64` | `15` |
|
||||
| [`beobachten_file`](#beobachten_file) | `String` | `"cache/beobachten.txt"` |
|
||||
| [`hardswap`](#hardswap) | `bool` | `true` |
|
||||
| [`me_warmup_stagger_enabled`](#me_warmup_stagger_enabled) | `bool` | `true` |
|
||||
| [`me_warmup_step_delay_ms`](#me_warmup_step_delay_ms) | `u64` | `500` |
|
||||
| [`me_warmup_step_jitter_ms`](#me_warmup_step_jitter_ms) | `u64` | `300` |
|
||||
| [`me_reconnect_max_concurrent_per_dc`](#me_reconnect_max_concurrent_per_dc) | `u32` | `8` |
|
||||
| [`me_reconnect_backoff_base_ms`](#me_reconnect_backoff_base_ms) | `u64` | `500` |
|
||||
| [`me_reconnect_backoff_cap_ms`](#me_reconnect_backoff_cap_ms) | `u64` | `30000` |
|
||||
| [`me_reconnect_fast_retry_count`](#me_reconnect_fast_retry_count) | `u32` | `16` |
|
||||
| [`me_single_endpoint_shadow_writers`](#me_single_endpoint_shadow_writers) | `u8` | `2` |
|
||||
| [`me_single_endpoint_outage_mode_enabled`](#me_single_endpoint_outage_mode_enabled) | `bool` | `true` |
|
||||
| [`me_single_endpoint_outage_disable_quarantine`](#me_single_endpoint_outage_disable_quarantine) | `bool` | `true` |
|
||||
| [`me_single_endpoint_outage_backoff_min_ms`](#me_single_endpoint_outage_backoff_min_ms) | `u64` | `250` |
|
||||
| [`me_single_endpoint_outage_backoff_max_ms`](#me_single_endpoint_outage_backoff_max_ms) | `u64` | `3000` |
|
||||
| [`me_single_endpoint_shadow_rotate_every_secs`](#me_single_endpoint_shadow_rotate_every_secs) | `u64` | `900` |
|
||||
| [`me_floor_mode`](#me_floor_mode) | `"static"` or `"adaptive"` | `"adaptive"` |
|
||||
| [`me_adaptive_floor_idle_secs`](#me_adaptive_floor_idle_secs) | `u64` | `90` |
|
||||
| [`me_adaptive_floor_min_writers_single_endpoint`](#me_adaptive_floor_min_writers_single_endpoint) | `u8` | `1` |
|
||||
| [`me_adaptive_floor_min_writers_multi_endpoint`](#me_adaptive_floor_min_writers_multi_endpoint) | `u8` | `1` |
|
||||
| [`me_adaptive_floor_recover_grace_secs`](#me_adaptive_floor_recover_grace_secs) | `u64` | `180` |
|
||||
| [`me_adaptive_floor_writers_per_core_total`](#me_adaptive_floor_writers_per_core_total) | `u16` | `48` |
|
||||
| [`me_adaptive_floor_cpu_cores_override`](#me_adaptive_floor_cpu_cores_override) | `u16` | `0` |
|
||||
| [`me_adaptive_floor_max_extra_writers_single_per_core`](#me_adaptive_floor_max_extra_writers_single_per_core) | `u16` | `1` |
|
||||
| [`me_adaptive_floor_max_extra_writers_multi_per_core`](#me_adaptive_floor_max_extra_writers_multi_per_core) | `u16` | `2` |
|
||||
| [`me_adaptive_floor_max_active_writers_per_core`](#me_adaptive_floor_max_active_writers_per_core) | `u16` | `64` |
|
||||
| [`me_adaptive_floor_max_warm_writers_per_core`](#me_adaptive_floor_max_warm_writers_per_core) | `u16` | `64` |
|
||||
| [`me_adaptive_floor_max_active_writers_global`](#me_adaptive_floor_max_active_writers_global) | `u32` | `256` |
|
||||
| [`me_adaptive_floor_max_warm_writers_global`](#me_adaptive_floor_max_warm_writers_global) | `u32` | `256` |
|
||||
| [`upstream_connect_retry_attempts`](#upstream_connect_retry_attempts) | `u32` | `2` |
|
||||
| [`upstream_connect_retry_backoff_ms`](#upstream_connect_retry_backoff_ms) | `u64` | `100` |
|
||||
| [`upstream_connect_budget_ms`](#upstream_connect_budget_ms) | `u64` | `3000` |
|
||||
| [`upstream_unhealthy_fail_threshold`](#upstream_unhealthy_fail_threshold) | `u32` | `5` |
|
||||
| [`upstream_connect_failfast_hard_errors`](#upstream_connect_failfast_hard_errors) | `bool` | `false` |
|
||||
| [`stun_iface_mismatch_ignore`](#stun_iface_mismatch_ignore) | `bool` | `false` |
|
||||
| [`unknown_dc_log_path`](#unknown_dc_log_path) | `String` | `"unknown-dc.txt"` |
|
||||
| [`unknown_dc_file_log_enabled`](#unknown_dc_file_log_enabled) | `bool` | `false` |
|
||||
| [`log_level`](#log_level) | `"debug"`, `"verbose"`, `"normal"`, or `"silent"` | `"normal"` |
|
||||
| [`disable_colors`](#disable_colors) | `bool` | `false` |
|
||||
| [`me_socks_kdf_policy`](#me_socks_kdf_policy) | `"strict"` or `"compat"` | `"strict"` |
|
||||
| [`me_route_backpressure_enabled`](#me_route_backpressure_enabled) | `bool` | `false` |
|
||||
| [`me_route_fairshare_enabled`](#me_route_fairshare_enabled) | `bool` | `false` |
|
||||
| [`me_route_backpressure_base_timeout_ms`](#me_route_backpressure_base_timeout_ms) | `u64` | `25` |
|
||||
| [`me_route_backpressure_high_timeout_ms`](#me_route_backpressure_high_timeout_ms) | `u64` | `120` |
|
||||
| [`me_route_backpressure_high_watermark_pct`](#me_route_backpressure_high_watermark_pct) | `u8` | `80` |
|
||||
| [`me_health_interval_ms_unhealthy`](#me_health_interval_ms_unhealthy) | `u64` | `1000` |
|
||||
| [`me_health_interval_ms_healthy`](#me_health_interval_ms_healthy) | `u64` | `3000` |
|
||||
| [`me_admission_poll_ms`](#me_admission_poll_ms) | `u64` | `1000` |
|
||||
| [`me_warn_rate_limit_ms`](#me_warn_rate_limit_ms) | `u64` | `5000` |
|
||||
| [`me_route_no_writer_mode`](#me_route_no_writer_mode) | `"async_recovery_failfast"`, `"inline_recovery_legacy"`, or `"hybrid_async_persistent"` | `"hybrid_async_persistent"` |
|
||||
| [`me_route_no_writer_wait_ms`](#me_route_no_writer_wait_ms) | `u64` | `250` |
|
||||
| [`me_route_hybrid_max_wait_ms`](#me_route_hybrid_max_wait_ms) | `u64` | `3000` |
|
||||
| [`me_route_blocking_send_timeout_ms`](#me_route_blocking_send_timeout_ms) | `u64` | `250` |
|
||||
| [`me_route_inline_recovery_attempts`](#me_route_inline_recovery_attempts) | `u32` | `3` |
|
||||
| [`me_route_inline_recovery_wait_ms`](#me_route_inline_recovery_wait_ms) | `u64` | `3000` |
|
||||
| [`fast_mode_min_tls_record`](#fast_mode_min_tls_record) | `usize` | `0` |
|
||||
| [`update_every`](#update_every) | `u64` | `300` |
|
||||
| [`me_reinit_every_secs`](#me_reinit_every_secs) | `u64` | `900` |
|
||||
| [`me_hardswap_warmup_delay_min_ms`](#me_hardswap_warmup_delay_min_ms) | `u64` | `1000` |
|
||||
| [`me_hardswap_warmup_delay_max_ms`](#me_hardswap_warmup_delay_max_ms) | `u64` | `2000` |
|
||||
| [`me_hardswap_warmup_extra_passes`](#me_hardswap_warmup_extra_passes) | `u8` | `3` |
|
||||
| [`me_hardswap_warmup_pass_backoff_base_ms`](#me_hardswap_warmup_pass_backoff_base_ms) | `u64` | `500` |
|
||||
| [`me_config_stable_snapshots`](#me_config_stable_snapshots) | `u8` | `2` |
|
||||
| [`me_config_apply_cooldown_secs`](#me_config_apply_cooldown_secs) | `u64` | `300` |
|
||||
| [`me_snapshot_require_http_2xx`](#me_snapshot_require_http_2xx) | `bool` | `true` |
|
||||
| [`me_snapshot_reject_empty_map`](#me_snapshot_reject_empty_map) | `bool` | `true` |
|
||||
| [`me_snapshot_min_proxy_for_lines`](#me_snapshot_min_proxy_for_lines) | `u32` | `1` |
|
||||
| [`proxy_secret_stable_snapshots`](#proxy_secret_stable_snapshots) | `u8` | `2` |
|
||||
| [`proxy_secret_rotate_runtime`](#proxy_secret_rotate_runtime) | `bool` | `true` |
|
||||
| [`me_secret_atomic_snapshot`](#me_secret_atomic_snapshot) | `bool` | `true` |
|
||||
| [`proxy_secret_len_max`](#proxy_secret_len_max) | `usize` | `256` |
|
||||
| [`me_pool_drain_ttl_secs`](#me_pool_drain_ttl_secs) | `u64` | `90` |
|
||||
| [`me_instadrain`](#me_instadrain) | `bool` | `false` |
|
||||
| [`me_pool_drain_threshold`](#me_pool_drain_threshold) | `u64` | `32` |
|
||||
| [`me_pool_drain_soft_evict_enabled`](#me_pool_drain_soft_evict_enabled) | `bool` | `true` |
|
||||
| [`me_pool_drain_soft_evict_grace_secs`](#me_pool_drain_soft_evict_grace_secs) | `u64` | `10` |
|
||||
| [`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_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_ttl_secs`](#me_bind_stale_ttl_secs) | `u64` | `90` |
|
||||
| [`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` |
|
||||
| [`proxy_secret_auto_reload_secs`](#proxy_secret_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_trigger_channel`](#me_reinit_trigger_channel) | `usize` | `64` |
|
||||
| [`me_reinit_coalesce_window_ms`](#me_reinit_coalesce_window_ms) | `u64` | `200` |
|
||||
| [`me_deterministic_writer_sort`](#me_deterministic_writer_sort) | `bool` | `true` |
|
||||
| [`me_writer_pick_mode`](#me_writer_pick_mode) | `"sorted_rr"` or `"p2c"` | `"p2c"` |
|
||||
| [`me_writer_pick_sample_size`](#me_writer_pick_sample_size) | `u8` | `3` |
|
||||
| [`ntp_check`](#ntp_check) | `bool` | `true` |
|
||||
| [`ntp_servers`](#ntp_servers) | `String[]` | `["pool.ntp.org"]` |
|
||||
| [`auto_degradation_enabled`](#auto_degradation_enabled) | `bool` | `true` |
|
||||
| [`degradation_min_unavailable_dc_groups`](#degradation_min_unavailable_dc_groups) | `u8` | `2` |
|
||||
| [`rst_on_close`](#rst_on_close) | `"off"`, `"errors"` или `"always"` | `"off"` |
|
||||
|
||||
## data_path
|
||||
- **Ограничения / валидация**: `String` (необязательный параметр).
|
||||
@@ -240,24 +228,6 @@
|
||||
[general]
|
||||
data_path = "/var/lib/telemt"
|
||||
```
|
||||
## quota_state_path
|
||||
- **Ограничения / валидация**: `Path`. Относительные пути разрешаются от рабочего каталога процесса.
|
||||
- **Описание**: JSON-файл состояния для сохранения runtime-расхода квот по пользователям.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
quota_state_path = "telemt.limit.json"
|
||||
```
|
||||
## config_strict
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
- **Описание**: Отклоняет неизвестные TOML-ключи во время загрузки конфигурации. При запуске процесс завершается с ошибкой; при hot-reload новый снимок отклоняется, а текущая конфигурация сохраняется.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
config_strict = true
|
||||
```
|
||||
## prefer_ipv6
|
||||
- **Ограничения / валидация**: Устарело. Используйте `network.prefer`.
|
||||
- **Описание**: Устаревший флаг предпочтения IPv6 перенесен в `network.prefer`.
|
||||
@@ -935,15 +905,6 @@
|
||||
[general]
|
||||
upstream_connect_budget_ms = 3000
|
||||
```
|
||||
## tg_connect
|
||||
- **Ограничения / валидация**: Должно быть `> 0` (секунды).
|
||||
- **Описание**: Таймаут подключения к upstream-серверам Telegram.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
tg_connect = 10
|
||||
```
|
||||
## upstream_unhealthy_fail_threshold
|
||||
- **Ограничения / валидация**: Должно быть `> 0`.
|
||||
- **Описание**: Количество неудачных запросов подряд, после которого upstream помечается, как неработоспособный.
|
||||
@@ -1561,11 +1522,11 @@
|
||||
# [general.modes]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`classic`](#classic) | `bool` | `false` | `✘` |
|
||||
| [`secure`](#secure) | `bool` | `false` | `✘` |
|
||||
| [`tls`](#tls) | `bool` | `true` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`classic`](#classic) | `bool` | `false` |
|
||||
| [`secure`](#secure) | `bool` | `false` |
|
||||
| [`tls`](#tls) | `bool` | `true` |
|
||||
|
||||
## classic
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -1599,11 +1560,11 @@
|
||||
# [general.links]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` | `✘` |
|
||||
| [`public_host`](#public_host) | `String` | — | `✘` |
|
||||
| [`public_port`](#public_port) | `u16` | — | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` |
|
||||
| [`public_host`](#public_host) | `String` | — |
|
||||
| [`public_port`](#public_port) | `u16` | — |
|
||||
|
||||
## show
|
||||
- **Ограничения / валидация**: `"*"` или `String[]`. Пустое значение означает, что нельзя показывать никому.
|
||||
@@ -1639,11 +1600,11 @@
|
||||
# [general.telemetry]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`core_enabled`](#core_enabled) | `bool` | `true` | `✔` |
|
||||
| [`user_enabled`](#user_enabled) | `bool` | `true` | `✔` |
|
||||
| [`me_level`](#me_level) | `"silent"`, `"normal"`, or `"debug"` | `"normal"` | `✔` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`core_enabled`](#core_enabled) | `bool` | `true` |
|
||||
| [`user_enabled`](#user_enabled) | `bool` | `true` |
|
||||
| [`me_level`](#me_level) | `"silent"`, `"normal"`, or `"debug"` | `"normal"` |
|
||||
|
||||
## core_enabled
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -1677,18 +1638,18 @@
|
||||
# [network]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`ipv4`](#ipv4) | `bool` | `true` | `✘` |
|
||||
| [`ipv6`](#ipv6) | `bool` | `false` | `✘` |
|
||||
| [`prefer`](#prefer) | `u8` | `4` | `✘` |
|
||||
| [`multipath`](#multipath) | `bool` | `false` | `✘` |
|
||||
| [`stun_use`](#stun_use) | `bool` | `true` | `✘` |
|
||||
| [`stun_servers`](#stun_servers) | `String[]` | Встроенный STUN-лист (13 записей) | `✘` |
|
||||
| [`stun_tcp_fallback`](#stun_tcp_fallback) | `bool` | `true` | `✘` |
|
||||
| [`http_ip_detect_urls`](#http_ip_detect_urls) | `String[]` | `["https://ifconfig.me/ip", "https://api.ipify.org"]` | `✘` |
|
||||
| [`cache_public_ip_path`](#cache_public_ip_path) | `String` | `"cache/public_ip.txt"` | `✘` |
|
||||
| [`dns_overrides`](#dns_overrides) | `String[]` | `[]` | `✔` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`ipv4`](#ipv4) | `bool` | `true` |
|
||||
| [`ipv6`](#ipv6) | `bool` | `false` |
|
||||
| [`prefer`](#prefer) | `u8` | `4` |
|
||||
| [`multipath`](#multipath) | `bool` | `false` |
|
||||
| [`stun_use`](#stun_use) | `bool` | `true` |
|
||||
| [`stun_servers`](#stun_servers) | `String[]` | Встроенный STUN-лист (13 записей) |
|
||||
| [`stun_tcp_fallback`](#stun_tcp_fallback) | `bool` | `true` |
|
||||
| [`http_ip_detect_urls`](#http_ip_detect_urls) | `String[]` | `["https://ifconfig.me/ip", "https://api.ipify.org"]` |
|
||||
| [`cache_public_ip_path`](#cache_public_ip_path) | `String` | `"cache/public_ip.txt"` |
|
||||
| [`dns_overrides`](#dns_overrides) | `String[]` | `[]` |
|
||||
|
||||
## ipv4
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -1798,27 +1759,23 @@
|
||||
# [server]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`port`](#port) | `u16` | `443` | `✘` |
|
||||
| [`listen_addr_ipv4`](#listen_addr_ipv4) | `String` | `"0.0.0.0"` | `✘` |
|
||||
| [`listen_addr_ipv6`](#listen_addr_ipv6) | `String` | `"::"` | `✘` |
|
||||
| [`listen_unix_sock`](#listen_unix_sock) | `String` | — | `✘` |
|
||||
| [`listen_unix_sock_perm`](#listen_unix_sock_perm) | `String` | — | `✘` |
|
||||
| [`listen_tcp`](#listen_tcp) | `bool` | — (auto) | `✘` |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | `false` | `✘` |
|
||||
| [`proxy_protocol_header_timeout_ms`](#proxy_protocol_header_timeout_ms) | `u64` | `500` | `✘` |
|
||||
| [`proxy_protocol_trusted_cidrs`](#proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` | `✘` |
|
||||
| [`metrics_port`](#metrics_port) | `u16` | — | `✘` |
|
||||
| [`metrics_listen`](#metrics_listen) | `String` | — | `✘` |
|
||||
| [`metrics_whitelist`](#metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` | `✘` |
|
||||
| [`api`](#serverapi) | `Table` | встроенные значения | `✘` |
|
||||
| [`admin_api`](#serverapi) | `Table` | алиас для `api` | `✘` |
|
||||
| [`listeners`](#serverlisteners) | `Table[]` | выводится из legacy listener-полей | `✘` |
|
||||
| [`max_connections`](#max_connections) | `u32` | `10000` | `✘` |
|
||||
| [`accept_permit_timeout_ms`](#accept_permit_timeout_ms) | `u64` | `250` | `✘` |
|
||||
| [`listen_backlog`](#listen_backlog) | `u32` | `1024` | `✘` |
|
||||
| [`conntrack_control`](#serverconntrack_control) | `Table` | встроенные значения | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`port`](#port) | `u16` | `443` |
|
||||
| [`listen_addr_ipv4`](#listen_addr_ipv4) | `String` | `"0.0.0.0"` |
|
||||
| [`listen_addr_ipv6`](#listen_addr_ipv6) | `String` | `"::"` |
|
||||
| [`listen_unix_sock`](#listen_unix_sock) | `String` | — |
|
||||
| [`listen_unix_sock_perm`](#listen_unix_sock_perm) | `String` | — |
|
||||
| [`listen_tcp`](#listen_tcp) | `bool` | — (auto) |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | `false` |
|
||||
| [`proxy_protocol_header_timeout_ms`](#proxy_protocol_header_timeout_ms) | `u64` | `500` |
|
||||
| [`proxy_protocol_trusted_cidrs`](#proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` |
|
||||
| [`metrics_port`](#metrics_port) | `u16` | — |
|
||||
| [`metrics_listen`](#metrics_listen) | `String` | — |
|
||||
| [`metrics_whitelist`](#metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` |
|
||||
| [`max_connections`](#max_connections) | `u32` | `10000` |
|
||||
| [`accept_permit_timeout_ms`](#accept_permit_timeout_ms) | `u64` | `250` |
|
||||
| [`listen_backlog`](#listen_backlog) | `u32` | `1024` |
|
||||
|
||||
## port
|
||||
- **Ограничения / валидация**: `u16`.
|
||||
@@ -1974,16 +1931,16 @@
|
||||
Примечание. Рабочий процесс `conntrack-control` работает **только в Linux**. В других операционных системах не запускается; если inline_conntrack_control имеет значение `true`, в логи записывается предупреждение. Для эффективной работы также требуется **CAP_NET_ADMIN** и пригодный к использованию бэкенд (nft или iptables/ip6tables в PATH). Утилита `conntrack` используется для удаления необязательных записей таблицы под нагрузкой.
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`inline_conntrack_control`](#inline_conntrack_control) | `bool` | `true` | `✘` |
|
||||
| [`mode`](#mode) | `String` | `"tracked"` | `✘` |
|
||||
| [`backend`](#backend) | `String` | `"auto"` | `✘` |
|
||||
| [`profile`](#profile) | `String` | `"balanced"` | `✘` |
|
||||
| [`hybrid_listener_ips`](#hybrid_listener_ips) | `IpAddr[]` | `[]` | `✘` |
|
||||
| [`pressure_high_watermark_pct`](#pressure_high_watermark_pct) | `u8` | `85` | `✘` |
|
||||
| [`pressure_low_watermark_pct`](#pressure_low_watermark_pct) | `u8` | `70` | `✘` |
|
||||
| [`delete_budget_per_sec`](#delete_budget_per_sec) | `u64` | `4096` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`inline_conntrack_control`](#inline_conntrack_control) | `bool` | `true` |
|
||||
| [`mode`](#mode) | `String` | `"tracked"` |
|
||||
| [`backend`](#backend) | `String` | `"auto"` |
|
||||
| [`profile`](#profile) | `String` | `"balanced"` |
|
||||
| [`hybrid_listener_ips`](#hybrid_listener_ips) | `IpAddr[]` | `[]` |
|
||||
| [`pressure_high_watermark_pct`](#pressure_high_watermark_pct) | `u8` | `85` |
|
||||
| [`pressure_low_watermark_pct`](#pressure_low_watermark_pct) | `u8` | `70` |
|
||||
| [`delete_budget_per_sec`](#delete_budget_per_sec) | `u64` | `4096` |
|
||||
|
||||
## inline_conntrack_control
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -2070,21 +2027,21 @@
|
||||
Примечание: В этом разделе также задается устаревший параметр `[server.admin_api]` (аналогично `[server.api]`).
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`enabled`](#enabled) | `bool` | `true` | `✘` |
|
||||
| [`listen`](#listen) | `String` | `"0.0.0.0:9091"` | `✘` |
|
||||
| [`whitelist`](#whitelist) | `IpNetwork[]` | `["127.0.0.0/8"]` | `✘` |
|
||||
| [`auth_header`](#auth_header) | `String` | `""` | `✘` |
|
||||
| [`request_body_limit_bytes`](#request_body_limit_bytes) | `usize` | `65536` | `✘` |
|
||||
| [`minimal_runtime_enabled`](#minimal_runtime_enabled) | `bool` | `true` | `✘` |
|
||||
| [`minimal_runtime_cache_ttl_ms`](#minimal_runtime_cache_ttl_ms) | `u64` | `1000` | `✘` |
|
||||
| [`runtime_edge_enabled`](#runtime_edge_enabled) | `bool` | `false` | `✘` |
|
||||
| [`runtime_edge_cache_ttl_ms`](#runtime_edge_cache_ttl_ms) | `u64` | `1000` | `✘` |
|
||||
| [`runtime_edge_top_n`](#runtime_edge_top_n) | `usize` | `10` | `✘` |
|
||||
| [`runtime_edge_events_capacity`](#runtime_edge_events_capacity) | `usize` | `256` | `✘` |
|
||||
| [`read_only`](#read_only) | `bool` | `false` | `✘` |
|
||||
| [`gray_action`](#gray_action) | `"drop"`, `"api"`, or `"200"` | `"drop"` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`enabled`](#enabled) | `bool` | `true` |
|
||||
| [`listen`](#listen) | `String` | `"0.0.0.0:9091"` |
|
||||
| [`whitelist`](#whitelist) | `IpNetwork[]` | `["127.0.0.0/8"]` |
|
||||
| [`auth_header`](#auth_header) | `String` | `""` |
|
||||
| [`request_body_limit_bytes`](#request_body_limit_bytes) | `usize` | `65536` |
|
||||
| [`minimal_runtime_enabled`](#minimal_runtime_enabled) | `bool` | `true` |
|
||||
| [`minimal_runtime_cache_ttl_ms`](#minimal_runtime_cache_ttl_ms) | `u64` | `1000` |
|
||||
| [`runtime_edge_enabled`](#runtime_edge_enabled) | `bool` | `false` |
|
||||
| [`runtime_edge_cache_ttl_ms`](#runtime_edge_cache_ttl_ms) | `u64` | `1000` |
|
||||
| [`runtime_edge_top_n`](#runtime_edge_top_n) | `usize` | `10` |
|
||||
| [`runtime_edge_events_capacity`](#runtime_edge_events_capacity) | `usize` | `256` |
|
||||
| [`read_only`](#read_only) | `bool` | `false` |
|
||||
| [`gray_action`](#gray_action) | `"drop"`, `"api"`, or `"200"` | `"drop"` |
|
||||
|
||||
## enabled
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -2208,14 +2165,13 @@
|
||||
# [[server.listeners]]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`ip`](#ip) | `IpAddr` | — | `✘` |
|
||||
| [`port`](#port-serverlisteners) | `u16` | `server.port` | `✘` |
|
||||
| [`announce`](#announce) | `String` | — | `✘` |
|
||||
| [`announce_ip`](#announce_ip) | `IpAddr` | — | `✘` |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | — | `✘` |
|
||||
| [`reuse_allow`](#reuse_allow) | `bool` | `false` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`ip`](#ip) | `IpAddr` | — |
|
||||
| [`announce`](#announce) | `String` | — |
|
||||
| [`announce_ip`](#announce_ip) | `IpAddr` | — |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | — |
|
||||
| [`reuse_allow`](#reuse_allow) | `bool` | `false` |
|
||||
|
||||
## ip
|
||||
- **Ограничения / валидация**: Обязательный параметр. Значение должно содержать IP-адрес в формате строки.
|
||||
@@ -2226,16 +2182,6 @@
|
||||
[[server.listeners]]
|
||||
ip = "0.0.0.0"
|
||||
```
|
||||
## port (server.listeners)
|
||||
- **Ограничения / валидация**: `u16` (необязательный параметр). Если не задан, используется `server.port`.
|
||||
- **Описание**: TCP-порт для конкретного listener’а.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[[server.listeners]]
|
||||
ip = "0.0.0.0"
|
||||
port = 443
|
||||
```
|
||||
## announce
|
||||
- **Ограничения / валидация**: `String` (необязательный параметр). Не должен быть пустым, если задан.
|
||||
- **Описание**: Публичный IP-адрес или домен, объявляемый в proxy-ссылках для данного listener’а. Имеет приоритет над `announce_ip`.
|
||||
@@ -2269,7 +2215,8 @@
|
||||
ip = "0.0.0.0"
|
||||
proxy_protocol = true
|
||||
```
|
||||
## reuse_allow
|
||||
## reuse_allow"
|
||||
- `reuse_allow`
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
- **Описание**: Включает `SO_REUSEPORT` для совместного использования привязки нескольких экземпляров (позволяет нескольким экземплярам telemt прослушивать один и тот же `ip:port`).
|
||||
- **Пример**:
|
||||
@@ -2284,18 +2231,18 @@
|
||||
# [timeouts]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`client_first_byte_idle_secs`](#client_first_byte_idle_secs) | `u64` | `300` | `✘` |
|
||||
| [`client_handshake`](#client_handshake) | `u64` | `30` | `✘` |
|
||||
| [`relay_idle_policy_v2_enabled`](#relay_idle_policy_v2_enabled) | `bool` | `true` | `✘` |
|
||||
| [`relay_client_idle_soft_secs`](#relay_client_idle_soft_secs) | `u64` | `120` | `✘` |
|
||||
| [`relay_client_idle_hard_secs`](#relay_client_idle_hard_secs) | `u64` | `360` | `✘` |
|
||||
| [`relay_idle_grace_after_downstream_activity_secs`](#relay_idle_grace_after_downstream_activity_secs) | `u64` | `30` | `✘` |
|
||||
| [`client_keepalive`](#client_keepalive) | `u64` | `15` | `✘` |
|
||||
| [`client_ack`](#client_ack) | `u64` | `90` | `✘` |
|
||||
| [`me_one_retry`](#me_one_retry) | `u8` | `12` | `✘` |
|
||||
| [`me_one_timeout_ms`](#me_one_timeout_ms) | `u64` | `1200` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`client_handshake`](#client_handshake) | `u64` | `30` |
|
||||
| [`relay_idle_policy_v2_enabled`](#relay_idle_policy_v2_enabled) | `bool` | `true` |
|
||||
| [`relay_client_idle_soft_secs`](#relay_client_idle_soft_secs) | `u64` | `120` |
|
||||
| [`relay_client_idle_hard_secs`](#relay_client_idle_hard_secs) | `u64` | `360` |
|
||||
| [`relay_idle_grace_after_downstream_activity_secs`](#relay_idle_grace_after_downstream_activity_secs) | `u64` | `30` |
|
||||
| [`tg_connect`](#tg_connect) | `u64` | `10` |
|
||||
| [`client_keepalive`](#client_keepalive) | `u64` | `15` |
|
||||
| [`client_ack`](#client_ack) | `u64` | `90` |
|
||||
| [`me_one_retry`](#me_one_retry) | `u8` | `12` |
|
||||
| [`me_one_timeout_ms`](#me_one_timeout_ms) | `u64` | `1200` |
|
||||
|
||||
## client_handshake
|
||||
- **Ограничения / валидация**: Должно быть `> 0`. Значение указано в секундах. Также используется в качестве верхней границы некоторых задержек эмуляции TLS (см. `censorship.server_hello_delay_max_ms`).
|
||||
@@ -2351,6 +2298,15 @@
|
||||
[timeouts]
|
||||
relay_idle_grace_after_downstream_activity_secs = 30
|
||||
```
|
||||
## tg_connect
|
||||
- **Ограничения / валидация**: `u64` (секунд).
|
||||
- **Описание**: Таймаут подключения к upstream-серверу Telegram (в секундах).
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[timeouts]
|
||||
tg_connect = 10
|
||||
```
|
||||
## client_keepalive
|
||||
- **Ограничения / валидация**: `u64` (секунд).
|
||||
- **Описание**: Таймаут keepalive для клиента..
|
||||
@@ -2392,40 +2348,40 @@
|
||||
# [censorship]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`tls_domain`](#tls_domain) | `String` | `"petrovich.ru"` | `✘` |
|
||||
| [`tls_domains`](#tls_domains) | `String[]` | `[]` | `✘` |
|
||||
| [`unknown_sni_action`](#unknown_sni_action) | `"drop"`, `"mask"`, `"accept"`, `"reject_handshake"` | `"drop"` | `✘` |
|
||||
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` | `✘` |
|
||||
| [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults | `✘` |
|
||||
| [`mask`](#mask) | `bool` | `true` | `✘` |
|
||||
| [`mask_host`](#mask_host) | `String` | — | `✘` |
|
||||
| [`mask_port`](#mask_port) | `u16` | `443` | `✘` |
|
||||
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — | `✘` |
|
||||
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` | `✘` |
|
||||
| [`tls_emulation`](#tls_emulation) | `bool` | `true` | `✘` |
|
||||
| [`tls_front_dir`](#tls_front_dir) | `String` | `"tlsfront"` | `✘` |
|
||||
| [`server_hello_delay_min_ms`](#server_hello_delay_min_ms) | `u64` | `0` | `✘` |
|
||||
| [`server_hello_delay_max_ms`](#server_hello_delay_max_ms) | `u64` | `0` | `✘` |
|
||||
| [`tls_new_session_tickets`](#tls_new_session_tickets) | `u8` | `0` | `✘` |
|
||||
| [`tls_full_cert_ttl_secs`](#tls_full_cert_ttl_secs) | `u64` | `90` | `✘` |
|
||||
| [`serverhello_compact`](#serverhello_compact) | `bool` | `false` | `✘` |
|
||||
| [`alpn_enforce`](#alpn_enforce) | `bool` | `true` | `✘` |
|
||||
| [`mask_proxy_protocol`](#mask_proxy_protocol) | `u8` | `0` | `✘` |
|
||||
| [`mask_shape_hardening`](#mask_shape_hardening) | `bool` | `true` | `✘` |
|
||||
| [`mask_shape_hardening_aggressive_mode`](#mask_shape_hardening_aggressive_mode) | `bool` | `false` | `✘` |
|
||||
| [`mask_shape_bucket_floor_bytes`](#mask_shape_bucket_floor_bytes) | `usize` | `512` | `✘` |
|
||||
| [`mask_shape_bucket_cap_bytes`](#mask_shape_bucket_cap_bytes) | `usize` | `4096` | `✘` |
|
||||
| [`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_relay_max_bytes`](#mask_relay_max_bytes) | `usize` | `5242880` | `✘` |
|
||||
| [`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_classifier_prefetch_timeout_ms`](#mask_classifier_prefetch_timeout_ms) | `u64` | `5` | `✘` |
|
||||
| [`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_ceiling_ms`](#mask_timing_normalization_ceiling_ms) | `u64` | `0` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`tls_domain`](#tls_domain) | `String` | `"petrovich.ru"` |
|
||||
| [`tls_domains`](#tls_domains) | `String[]` | `[]` |
|
||||
| [`unknown_sni_action`](#unknown_sni_action) | `"drop"`, `"mask"`, `"accept"`, `"reject_handshake"` | `"drop"` |
|
||||
| [`tls_fetch_scope`](#tls_fetch_scope) | `String` | `""` |
|
||||
| [`tls_fetch`](#tls_fetch) | `Table` | built-in defaults |
|
||||
| [`mask`](#mask) | `bool` | `true` |
|
||||
| [`mask_host`](#mask_host) | `String` | — |
|
||||
| [`mask_port`](#mask_port) | `u16` | `443` |
|
||||
| [`mask_unix_sock`](#mask_unix_sock) | `String` | — |
|
||||
| [`fake_cert_len`](#fake_cert_len) | `usize` | `2048` |
|
||||
| [`tls_emulation`](#tls_emulation) | `bool` | `true` |
|
||||
| [`tls_front_dir`](#tls_front_dir) | `String` | `"tlsfront"` |
|
||||
| [`server_hello_delay_min_ms`](#server_hello_delay_min_ms) | `u64` | `0` |
|
||||
| [`server_hello_delay_max_ms`](#server_hello_delay_max_ms) | `u64` | `0` |
|
||||
| [`tls_new_session_tickets`](#tls_new_session_tickets) | `u8` | `0` |
|
||||
| [`tls_full_cert_ttl_secs`](#tls_full_cert_ttl_secs) | `u64` | `90` |
|
||||
| [`serverhello_compact`](#serverhello_compact) | `bool` | `false` |
|
||||
| [`alpn_enforce`](#alpn_enforce) | `bool` | `true` |
|
||||
| [`mask_proxy_protocol`](#mask_proxy_protocol) | `u8` | `0` |
|
||||
| [`mask_shape_hardening`](#mask_shape_hardening) | `bool` | `true` |
|
||||
| [`mask_shape_hardening_aggressive_mode`](#mask_shape_hardening_aggressive_mode) | `bool` | `false` |
|
||||
| [`mask_shape_bucket_floor_bytes`](#mask_shape_bucket_floor_bytes) | `usize` | `512` |
|
||||
| [`mask_shape_bucket_cap_bytes`](#mask_shape_bucket_cap_bytes) | `usize` | `4096` |
|
||||
| [`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_relay_max_bytes`](#mask_relay_max_bytes) | `usize` | `5242880` |
|
||||
| [`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_classifier_prefetch_timeout_ms`](#mask_classifier_prefetch_timeout_ms) | `u64` | `5` |
|
||||
| [`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_ceiling_ms`](#mask_timing_normalization_ceiling_ms) | `u64` | `0` |
|
||||
|
||||
## tls_domain
|
||||
- **Ограничения / валидация**: Не должно быть пустым. Не должно содержать пробелы или `/`.
|
||||
@@ -2848,15 +2804,15 @@
|
||||
# [censorship.tls_fetch]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`profiles`](#profiles) | `String[]` | `["modern_chrome_like", "modern_firefox_like", "compat_tls12", "legacy_minimal"]` | `✘` |
|
||||
| [`strict_route`](#strict_route) | `bool` | `true` | `✘` |
|
||||
| [`attempt_timeout_ms`](#attempt_timeout_ms) | `u64` | `5000` | `✘` |
|
||||
| [`total_budget_ms`](#total_budget_ms) | `u64` | `15000` | `✘` |
|
||||
| [`grease_enabled`](#grease_enabled) | `bool` | `false` | `✘` |
|
||||
| [`deterministic`](#deterministic) | `bool` | `false` | `✘` |
|
||||
| [`profile_cache_ttl_secs`](#profile_cache_ttl_secs) | `u64` | `600` | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`profiles`](#profiles) | `String[]` | `["modern_chrome_like", "modern_firefox_like", "compat_tls12", "legacy_minimal"]` |
|
||||
| [`strict_route`](#strict_route) | `bool` | `true` |
|
||||
| [`attempt_timeout_ms`](#attempt_timeout_ms) | `u64` | `5000` |
|
||||
| [`total_budget_ms`](#total_budget_ms) | `u64` | `15000` |
|
||||
| [`grease_enabled`](#grease_enabled) | `bool` | `false` |
|
||||
| [`deterministic`](#deterministic) | `bool` | `false` |
|
||||
| [`profile_cache_ttl_secs`](#profile_cache_ttl_secs) | `u64` | `600` |
|
||||
|
||||
## profiles
|
||||
- **Ограничения / валидация**: `String[]`. Пустой список возвращает значения по умолчанию; дубликаты удаляются с сохранением порядка.
|
||||
@@ -2925,24 +2881,23 @@
|
||||
# [access]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`users`](#users) | `Map<String, String>` | `{"default": "000…000"}` | `✔` |
|
||||
| [`user_ad_tags`](#user_ad_tags) | `Map<String, String>` | `{}` | `✔` |
|
||||
| [`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_expirations`](#user_expirations) | `Map<String, DateTime<Utc>>` | `{}` | `✔` |
|
||||
| [`user_data_quota`](#user_data_quota) | `Map<String, u64>` | `{}` | `✔` |
|
||||
| [`user_max_unique_ips`](#user_max_unique_ips) | `Map<String, usize>` | `{}` | `✔` |
|
||||
| [`user_max_unique_ips_global_each`](#user_max_unique_ips_global_each) | `usize` | `0` | `✔` |
|
||||
| [`user_max_unique_ips_mode`](#user_max_unique_ips_mode) | `"active_window"`, `"time_window"`, or `"combined"` | `"active_window"` | `✔` |
|
||||
| [`user_max_unique_ips_window_secs`](#user_max_unique_ips_window_secs) | `u64` | `30` | `✔` |
|
||||
| [`user_source_deny`](#user_source_deny) | `Map<String, IpNetwork[]>` | `{}` | `✘` |
|
||||
| [`replay_check_len`](#replay_check_len) | `usize` | `65536` | `✘` |
|
||||
| [`replay_window_secs`](#replay_window_secs) | `u64` | `120` | `✘` |
|
||||
| [`ignore_time_skew`](#ignore_time_skew) | `bool` | `false` | `✘` |
|
||||
| [`user_rate_limits`](#user_rate_limits) | `Map<String, RateLimitBps>` | `{}` | `✔` |
|
||||
| [`cidr_rate_limits`](#cidr_rate_limits) | `Map<IpNetwork, RateLimitBps>` | `{}` | `✔` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`users`](#users) | `Map<String, String>` | `{"default": "000…000"}` |
|
||||
| [`user_ad_tags`](#user_ad_tags) | `Map<String, String>` | `{}` |
|
||||
| [`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_expirations`](#user_expirations) | `Map<String, DateTime<Utc>>` | `{}` |
|
||||
| [`user_data_quota`](#user_data_quota) | `Map<String, u64>` | `{}` |
|
||||
| [`user_max_unique_ips`](#user_max_unique_ips) | `Map<String, usize>` | `{}` |
|
||||
| [`user_max_unique_ips_global_each`](#user_max_unique_ips_global_each) | `usize` | `0` |
|
||||
| [`user_max_unique_ips_mode`](#user_max_unique_ips_mode) | `"active_window"`, `"time_window"`, or `"combined"` | `"active_window"` |
|
||||
| [`user_max_unique_ips_window_secs`](#user_max_unique_ips_window_secs) | `u64` | `30` |
|
||||
| [`replay_check_len`](#replay_check_len) | `usize` | `65536` |
|
||||
| [`replay_window_secs`](#replay_window_secs) | `u64` | `120` |
|
||||
| [`ignore_time_skew`](#ignore_time_skew) | `bool` | `false` |
|
||||
| [`user_rate_limits`](#user_rate_limits) | `Map<String, RateLimitBps>` | `{}` |
|
||||
| [`cidr_rate_limits`](#cidr_rate_limits) | `Map<IpNetwork, RateLimitBps>` | `{}` |
|
||||
|
||||
## users
|
||||
- **Ограничения / валидация**: Не должно быть пустым (должен существовать хотя бы один пользователь). Каждое значение должно состоять **ровно из 32 шестнадцатеричных символов**.
|
||||
@@ -3042,20 +2997,6 @@
|
||||
[access]
|
||||
user_max_unique_ips_window_secs = 30
|
||||
```
|
||||
## user_source_deny
|
||||
- **Ограничения / валидация**: Таблица `username -> IpNetwork[]`. Каждая сеть должна разбираться как CIDR, например `203.0.113.0/24` или `2001:db8::/32`.
|
||||
- **Описание**: Deny-list исходных IP/CIDR для конкретного пользователя, применяемый **после успешной аутентификации** в TLS- и MTProto-handshake путях. Совпавший source IP отклоняется тем же fail-closed путём, что и невалидная аутентификация.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[access.user_source_deny]
|
||||
alice = ["203.0.113.0/24", "2001:db8:abcd::/48"]
|
||||
bob = ["198.51.100.42/32"]
|
||||
```
|
||||
|
||||
- **Краткая проверка**:
|
||||
- соединение пользователя `alice` с source `203.0.113.55` отклоняется, потому что совпадает с `203.0.113.0/24`;
|
||||
- соединение пользователя `alice` с source `198.51.100.10` допускается этим набором правил, потому что совпадений нет.
|
||||
## replay_check_len
|
||||
- **Ограничения / валидация**: `usize`.
|
||||
- **Описание**: Количество последних сообщений/запросов, которое система запоминает, чтобы не допустить их повторной отправки (replay).
|
||||
@@ -3106,23 +3047,19 @@
|
||||
# [[upstreams]]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`type`](#type) | `"direct"`, `"socks4"`, `"socks5"`, or `"shadowsocks"` | — | `✘` |
|
||||
| [`weight`](#weight) | `u16` | `1` | `✘` |
|
||||
| [`enabled`](#enabled) | `bool` | `true` | `✘` |
|
||||
| [`scopes`](#scopes) | `String` | `""` | `✘` |
|
||||
| [`ipv4`](#ipv4-upstreams) | `bool` | — (auto) | `✘` |
|
||||
| [`ipv6`](#ipv6-upstreams) | `bool` | — (auto) | `✘` |
|
||||
| [`interface`](#interface) | `String` | — | `✘` |
|
||||
| [`bind_addresses`](#bind_addresses) | `String[]` | — | `✘` |
|
||||
| [`bindtodevice`](#bindtodevice) | `String` | — | `✘` |
|
||||
| [`force_bind`](#force_bind) | `String` | — | `✘` |
|
||||
| [`url`](#url) | `String` | — | `✘` |
|
||||
| [`address`](#address) | `String` | — | `✘` |
|
||||
| [`user_id`](#user_id) | `String` | — | `✘` |
|
||||
| [`username`](#username) | `String` | — | `✘` |
|
||||
| [`password`](#password) | `String` | — | `✘` |
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`type`](#type) | `"direct"`, `"socks4"`, `"socks5"`, or `"shadowsocks"` | — |
|
||||
| [`weight`](#weight) | `u16` | `1` |
|
||||
| [`enabled`](#enabled) | `bool` | `true` |
|
||||
| [`scopes`](#scopes) | `String` | `""` |
|
||||
| [`interface`](#interface) | `String` | — |
|
||||
| [`bind_addresses`](#bind_addresses) | `String[]` | — |
|
||||
| [`url`](#url) | `String` | — |
|
||||
| [`address`](#address) | `String` | — |
|
||||
| [`user_id`](#user_id) | `String` | — |
|
||||
| [`username`](#username) | `String` | — |
|
||||
| [`password`](#password) | `String` | — |
|
||||
|
||||
## type
|
||||
- **Ограничения / валидация**: Обязательный параметр.`"direct"`, `"socks4"`, `"socks5"`, `"shadowsocks"`.
|
||||
@@ -3173,26 +3110,6 @@
|
||||
address = "10.0.0.10:1080"
|
||||
scopes = "me, fetch, dc2"
|
||||
```
|
||||
## ipv4 (upstreams)
|
||||
- **Ограничения / валидация**: `bool` (необязательный параметр).
|
||||
- **Описание**: Разрешает IPv4 DC-targets для этого upstream. Если не задан, Telemt определяет поддержку автоматически по runtime-состоянию connectivity.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
ipv4 = true
|
||||
```
|
||||
## ipv6 (upstreams)
|
||||
- **Ограничения / валидация**: `bool` (необязательный параметр).
|
||||
- **Описание**: Разрешает IPv6 DC-targets для этого upstream. Если не задан, Telemt определяет поддержку автоматически по runtime-состоянию connectivity.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
ipv6 = false
|
||||
```
|
||||
## interface
|
||||
- **Ограничения / валидация**: `String` (необязательный параметр).
|
||||
- для `"direct"`: может быть IP-адресом (используется как явный local bind) или именем сетевого интерфейса ОС (резолвится в IP во время выполнения; только Unix).
|
||||
@@ -3223,26 +3140,6 @@
|
||||
type = "direct"
|
||||
bind_addresses = ["192.0.2.10", "192.0.2.11"]
|
||||
```
|
||||
## bindtodevice
|
||||
- **Ограничения / валидация**: `String` (необязательный параметр). Применяется только для `type = "direct"` и только в Linux.
|
||||
- **Описание**: Жёсткая привязка исходящих direct TCP-connect к интерфейсу через `SO_BINDTODEVICE`.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
bindtodevice = "eth0"
|
||||
```
|
||||
## force_bind
|
||||
- **Ограничения / валидация**: `String` (необязательный параметр). Алиас для `bindtodevice`.
|
||||
- **Описание**: Обратно-совместимый алиас для жёсткой Linux-привязки к интерфейсу через `SO_BINDTODEVICE`.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[[upstreams]]
|
||||
type = "direct"
|
||||
force_bind = "eth0"
|
||||
```
|
||||
## url
|
||||
- **Ограничения / валидация**: Применяется в случае, если `type = "shadowsocks"`.
|
||||
- Должен быть действительный URL-адрес Shadowsocks, принятый `shadowsocks` контейнером.
|
||||
|
||||
@@ -254,6 +254,19 @@ docker compose down
|
||||
> - `docker-compose.yml` maps `./config.toml` to `/app/config.toml` (read-only)
|
||||
> - By default it publishes `443:443` and runs with dropped capabilities (only `NET_BIND_SERVICE` is added)
|
||||
> - If you really need host networking (usually only for some IPv6 setups) uncomment `network_mode: host`
|
||||
> - 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`.
|
||||
|
||||
Example writable config mount for Control API mutations:
|
||||
```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**
|
||||
```bash
|
||||
|
||||
55
install.sh
55
install.sh
@@ -105,7 +105,6 @@ set_language() {
|
||||
L_OUT_UNINST_H="УДАЛЕНИЕ ЗАВЕРШЕНО"
|
||||
L_OUT_LINK="Ваша ссылка для подключения к Telegram Proxy:\n"
|
||||
L_ERR_INCORR_ROOT_LOGIN="Используйте 'su -' или 'sudo -i' для входа под пользователем root"
|
||||
L_OUT_LOGS="Чтобы посмотреть логи (в случае проблем), используйте команду:"
|
||||
;;
|
||||
*)
|
||||
L_ERR_DOMAIN_REQ="requires a domain argument."
|
||||
@@ -181,7 +180,6 @@ set_language() {
|
||||
L_OUT_UNINST_H="UNINSTALLATION COMPLETE"
|
||||
L_OUT_LINK="Your Telegram Proxy connection link:\n"
|
||||
L_ERR_INCORR_ROOT_LOGIN="Use 'su -' or 'sudo -i' to login under root"
|
||||
L_OUT_LOGS="To view logs (in case of issues), use the following command:"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -394,7 +392,7 @@ verify_common() {
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=""
|
||||
if [ "${USER:-}" != "root" ] && [ "${LOGNAME:-}" != "root" ]; then
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
die "$L_ERR_INCORR_ROOT_LOGIN"
|
||||
fi
|
||||
else
|
||||
@@ -541,7 +539,7 @@ install_binary() {
|
||||
fi
|
||||
|
||||
$SUDO mkdir -p "$INSTALL_DIR" || die "$L_ERR_MKDIR"
|
||||
|
||||
|
||||
$SUDO rm -f "$bin_dst" 2>/dev/null || true
|
||||
|
||||
if command -v install >/dev/null 2>&1; then
|
||||
@@ -611,33 +609,33 @@ install_config() {
|
||||
|
||||
tmp_conf="${TEMP_DIR}/config.tmp"
|
||||
$SUDO cat "$CONFIG_FILE" > "$tmp_conf"
|
||||
|
||||
|
||||
escaped_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
|
||||
awk -v port="$SERVER_PORT" -v secret="$USER_SECRET" -v domain="$escaped_domain" -v ad_tag="$AD_TAG" \
|
||||
-v flag_p="$PORT_PROVIDED" -v flag_s="$SECRET_PROVIDED" -v flag_d="$DOMAIN_PROVIDED" -v flag_a="$AD_TAG_PROVIDED" '
|
||||
BEGIN { ad_tag_handled = 0 }
|
||||
|
||||
|
||||
flag_p == "1" && /^[ \t]*port[ \t]*=/ { print "port = " port; next }
|
||||
flag_s == "1" && /^[ \t]*hello[ \t]*=/ { print "hello = \"" secret "\""; next }
|
||||
flag_d == "1" && /^[ \t]*tls_domain[ \t]*=/ { print "tls_domain = \"" domain "\""; next }
|
||||
|
||||
flag_a == "1" && /^[ \t]*ad_tag[ \t]*=/ {
|
||||
if (!ad_tag_handled) {
|
||||
print "ad_tag = \"" ad_tag "\"";
|
||||
ad_tag_handled = 1;
|
||||
}
|
||||
next
|
||||
|
||||
flag_a == "1" && /^[ \t]*ad_tag[ \t]*=/ {
|
||||
if (!ad_tag_handled) {
|
||||
print "ad_tag = \"" ad_tag "\"";
|
||||
ad_tag_handled = 1;
|
||||
}
|
||||
next
|
||||
}
|
||||
flag_a == "1" && /^\[general\]/ {
|
||||
print;
|
||||
if (!ad_tag_handled) {
|
||||
print "ad_tag = \"" ad_tag "\"";
|
||||
ad_tag_handled = 1;
|
||||
}
|
||||
next
|
||||
flag_a == "1" && /^\[general\]/ {
|
||||
print;
|
||||
if (!ad_tag_handled) {
|
||||
print "ad_tag = \"" ad_tag "\"";
|
||||
ad_tag_handled = 1;
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
|
||||
{ print }
|
||||
' "$tmp_conf" > "${tmp_conf}.new" && mv "${tmp_conf}.new" "$tmp_conf"
|
||||
|
||||
@@ -787,11 +785,11 @@ uninstall() {
|
||||
say "$L_U_STAGE_5"
|
||||
$SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR"
|
||||
$SUDO rm -f "$CONFIG_FILE"
|
||||
|
||||
|
||||
if check_os_entity passwd telemt; then
|
||||
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true
|
||||
fi
|
||||
|
||||
|
||||
if check_os_entity group telemt; then
|
||||
$SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true
|
||||
fi
|
||||
@@ -918,7 +916,7 @@ case "$ACTION" in
|
||||
if command -v curl >/dev/null 2>&1; then SERVER_IP="$(curl -s4 -m 3 ifconfig.me 2>/dev/null || curl -s4 -m 3 api.ipify.org 2>/dev/null || true)"
|
||||
elif command -v wget >/dev/null 2>&1; then SERVER_IP="$(wget -qO- -T 3 ifconfig.me 2>/dev/null || wget -qO- -T 3 api.ipify.org 2>/dev/null || true)"; fi
|
||||
[ -z "$SERVER_IP" ] && SERVER_IP="<YOUR_SERVER_IP>"
|
||||
|
||||
|
||||
if command -v xxd >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | xxd -p | tr -d '\n')"
|
||||
elif command -v hexdump >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | hexdump -v -e '/1 "%02x"')"
|
||||
elif command -v od >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | od -A n -t x1 | tr -d ' \n')"
|
||||
@@ -929,15 +927,6 @@ case "$ACTION" in
|
||||
printf '%b\n' "$L_OUT_LINK"
|
||||
printf ' tg://proxy?server=%s&port=%s&secret=%s\n\n' "$SERVER_IP" "$SERVER_PORT" "$CLIENT_SECRET"
|
||||
|
||||
svc="$(get_svc_mgr)"
|
||||
if [ "$svc" = "systemd" ]; then
|
||||
printf '%s\n' "$L_OUT_LOGS"
|
||||
printf ' sudo journalctl -u %s -f\n\n' "$SERVICE_NAME"
|
||||
elif [ "$svc" = "openrc" ]; then
|
||||
printf '%s\n' "$L_OUT_LOGS"
|
||||
printf ' sudo tail -f /var/log/messages /var/log/syslog 2>/dev/null | grep -i %s\n\n' "$SERVICE_NAME"
|
||||
fi
|
||||
|
||||
printf '====================================================================\n'
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -7,7 +7,7 @@ use hyper::header::IF_MATCH;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::config::ProxyConfig;
|
||||
use crate::config::{ProxyConfig, RateLimitBps};
|
||||
|
||||
use super::model::ApiFailure;
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(super) enum AccessSection {
|
||||
UserMaxTcpConns,
|
||||
UserExpirations,
|
||||
UserDataQuota,
|
||||
UserRateLimits,
|
||||
UserMaxUniqueIps,
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ impl AccessSection {
|
||||
Self::UserMaxTcpConns => "access.user_max_tcp_conns",
|
||||
Self::UserExpirations => "access.user_expirations",
|
||||
Self::UserDataQuota => "access.user_data_quota",
|
||||
Self::UserRateLimits => "access.user_rate_limits",
|
||||
Self::UserMaxUniqueIps => "access.user_max_unique_ips",
|
||||
}
|
||||
}
|
||||
@@ -169,6 +171,15 @@ fn render_access_section(cfg: &ProxyConfig, section: AccessSection) -> Result<St
|
||||
.collect();
|
||||
serialize_table_body(&rows)?
|
||||
}
|
||||
AccessSection::UserRateLimits => {
|
||||
let rows: BTreeMap<String, RateLimitBps> = cfg
|
||||
.access
|
||||
.user_rate_limits
|
||||
.iter()
|
||||
.map(|(key, value)| (key.clone(), *value))
|
||||
.collect();
|
||||
serialize_rate_limit_body(&rows)?
|
||||
}
|
||||
AccessSection::UserMaxUniqueIps => {
|
||||
let rows: BTreeMap<String, usize> = cfg
|
||||
.access
|
||||
@@ -197,6 +208,7 @@ fn access_section_is_empty(cfg: &ProxyConfig, section: AccessSection) -> bool {
|
||||
AccessSection::UserMaxTcpConns => cfg.access.user_max_tcp_conns.is_empty(),
|
||||
AccessSection::UserExpirations => cfg.access.user_expirations.is_empty(),
|
||||
AccessSection::UserDataQuota => cfg.access.user_data_quota.is_empty(),
|
||||
AccessSection::UserRateLimits => cfg.access.user_rate_limits.is_empty(),
|
||||
AccessSection::UserMaxUniqueIps => cfg.access.user_max_unique_ips.is_empty(),
|
||||
}
|
||||
}
|
||||
@@ -206,6 +218,28 @@ fn serialize_table_body<T: Serialize>(value: &T) -> Result<String, ApiFailure> {
|
||||
.map_err(|e| ApiFailure::internal(format!("failed to serialize access section: {}", e)))
|
||||
}
|
||||
|
||||
fn serialize_rate_limit_body(rows: &BTreeMap<String, RateLimitBps>) -> Result<String, ApiFailure> {
|
||||
let mut out = String::new();
|
||||
for (key, value) in rows {
|
||||
let key = serialize_toml_key(key)?;
|
||||
out.push_str(&format!(
|
||||
"{key} = {{ up_bps = {}, down_bps = {} }}\n",
|
||||
value.up_bps, value.down_bps
|
||||
));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn serialize_toml_key(key: &str) -> Result<String, ApiFailure> {
|
||||
let mut row = BTreeMap::new();
|
||||
row.insert(key.to_string(), 0_u8);
|
||||
let rendered = serialize_table_body(&row)?;
|
||||
rendered
|
||||
.split_once(" = ")
|
||||
.map(|(key, _)| key.to_string())
|
||||
.ok_or_else(|| ApiFailure::internal("failed to serialize TOML key"))
|
||||
}
|
||||
|
||||
fn upsert_toml_table(source: &str, table_name: &str, replacement: &str) -> String {
|
||||
if let Some((start, end)) = find_toml_table_bounds(source, table_name) {
|
||||
let mut out = String::with_capacity(source.len() + replacement.len());
|
||||
@@ -285,3 +319,26 @@ fn write_atomic_sync(path: &Path, contents: &str) -> std::io::Result<()> {
|
||||
}
|
||||
write_result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn render_user_rate_limits_section() {
|
||||
let mut cfg = ProxyConfig::default();
|
||||
cfg.access.user_rate_limits.insert(
|
||||
"alice".to_string(),
|
||||
RateLimitBps {
|
||||
up_bps: 1024,
|
||||
down_bps: 2048,
|
||||
},
|
||||
);
|
||||
|
||||
let rendered = render_access_section(&cfg, AccessSection::UserRateLimits)
|
||||
.expect("section must render");
|
||||
|
||||
assert!(rendered.starts_with("[access.user_rate_limits]\n"));
|
||||
assert!(rendered.contains("alice = { up_bps = 1024, down_bps = 2048 }"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,9 @@ use runtime_zero::{
|
||||
build_limits_effective_data, build_runtime_gates_data, build_security_posture_data,
|
||||
build_system_info_data,
|
||||
};
|
||||
use users::{create_user, delete_user, patch_user, rotate_secret, users_from_config};
|
||||
use users::{
|
||||
build_user_quota_list, create_user, delete_user, patch_user, rotate_secret, users_from_config,
|
||||
};
|
||||
|
||||
const API_MAX_CONTROL_CONNECTIONS: usize = 1024;
|
||||
const API_HTTP_CONNECTION_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
@@ -504,6 +506,12 @@ async fn handle(
|
||||
.await;
|
||||
Ok(success_response(StatusCode::OK, users, revision))
|
||||
}
|
||||
("GET", "/v1/users/quota") => {
|
||||
let revision = current_revision(&shared.config_path).await?;
|
||||
let disk_cfg = load_config_from_disk(&shared.config_path).await?;
|
||||
let data = build_user_quota_list(&disk_cfg, shared.stats.as_ref());
|
||||
Ok(success_response(StatusCode::OK, data, revision))
|
||||
}
|
||||
("POST", "/v1/users") => {
|
||||
if api_cfg.read_only {
|
||||
return Ok(error_response(
|
||||
|
||||
@@ -473,6 +473,8 @@ pub(super) struct UserInfo {
|
||||
pub(super) max_tcp_conns: Option<usize>,
|
||||
pub(super) expiration_rfc3339: Option<String>,
|
||||
pub(super) data_quota_bytes: Option<u64>,
|
||||
pub(super) rate_limit_up_bps: Option<u64>,
|
||||
pub(super) rate_limit_down_bps: Option<u64>,
|
||||
pub(super) max_unique_ips: Option<usize>,
|
||||
pub(super) current_connections: u64,
|
||||
pub(super) active_unique_ips: usize,
|
||||
@@ -508,6 +510,19 @@ pub(super) struct ResetUserQuotaResponse {
|
||||
pub(super) last_reset_epoch_secs: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct UserQuotaListData {
|
||||
pub(super) users: Vec<UserQuotaEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct UserQuotaEntry {
|
||||
pub(super) username: String,
|
||||
pub(super) data_quota_bytes: u64,
|
||||
pub(super) used_bytes: u64,
|
||||
pub(super) last_reset_epoch_secs: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(super) struct CreateUserRequest {
|
||||
pub(super) username: String,
|
||||
@@ -516,6 +531,8 @@ pub(super) struct CreateUserRequest {
|
||||
pub(super) max_tcp_conns: Option<usize>,
|
||||
pub(super) expiration_rfc3339: Option<String>,
|
||||
pub(super) data_quota_bytes: Option<u64>,
|
||||
pub(super) rate_limit_up_bps: Option<u64>,
|
||||
pub(super) rate_limit_down_bps: Option<u64>,
|
||||
pub(super) max_unique_ips: Option<usize>,
|
||||
}
|
||||
|
||||
@@ -531,6 +548,10 @@ pub(super) struct PatchUserRequest {
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) data_quota_bytes: Patch<u64>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) rate_limit_up_bps: Patch<u64>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) rate_limit_down_bps: Patch<u64>,
|
||||
#[serde(default, deserialize_with = "patch_field")]
|
||||
pub(super) max_unique_ips: Patch<usize>,
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,9 @@ mod tests {
|
||||
"secret": "00112233445566778899aabbccddeeff",
|
||||
"max_tcp_conns": 0,
|
||||
"max_unique_ips": null,
|
||||
"data_quota_bytes": 1024
|
||||
"data_quota_bytes": 1024,
|
||||
"rate_limit_up_bps": 4096,
|
||||
"rate_limit_down_bps": null
|
||||
}"#;
|
||||
let req: PatchUserRequest = serde_json::from_str(raw).expect("valid json");
|
||||
assert_eq!(
|
||||
@@ -124,6 +126,8 @@ mod tests {
|
||||
assert!(matches!(req.max_tcp_conns, Patch::Set(0)));
|
||||
assert!(matches!(req.max_unique_ips, Patch::Remove));
|
||||
assert!(matches!(req.data_quota_bytes, Patch::Set(1024)));
|
||||
assert!(matches!(req.rate_limit_up_bps, Patch::Set(4096)));
|
||||
assert!(matches!(req.rate_limit_down_bps, Patch::Remove));
|
||||
assert!(matches!(req.expiration_rfc3339, Patch::Unchanged));
|
||||
assert!(matches!(req.user_ad_tag, Patch::Unchanged));
|
||||
}
|
||||
|
||||
186
src/api/users.rs
186
src/api/users.rs
@@ -3,6 +3,7 @@ use std::net::IpAddr;
|
||||
use hyper::StatusCode;
|
||||
|
||||
use crate::config::ProxyConfig;
|
||||
use crate::config::RateLimitBps;
|
||||
use crate::ip_tracker::UserIpTracker;
|
||||
use crate::stats::Stats;
|
||||
|
||||
@@ -13,8 +14,9 @@ use super::config_store::{
|
||||
};
|
||||
use super::model::{
|
||||
ApiFailure, CreateUserRequest, CreateUserResponse, PatchUserRequest, RotateSecretRequest,
|
||||
TlsDomainLink, UserInfo, UserLinks, is_valid_ad_tag, is_valid_user_secret, is_valid_username,
|
||||
parse_optional_expiration, parse_patch_expiration, random_user_secret,
|
||||
TlsDomainLink, UserInfo, UserLinks, UserQuotaEntry, UserQuotaListData, is_valid_ad_tag,
|
||||
is_valid_user_secret, is_valid_username, parse_optional_expiration, parse_patch_expiration,
|
||||
random_user_secret,
|
||||
};
|
||||
use super::patch::Patch;
|
||||
|
||||
@@ -27,6 +29,8 @@ pub(super) async fn create_user(
|
||||
let touches_user_max_tcp_conns = body.max_tcp_conns.is_some();
|
||||
let touches_user_expirations = body.expiration_rfc3339.is_some();
|
||||
let touches_user_data_quota = body.data_quota_bytes.is_some();
|
||||
let touches_user_rate_limits =
|
||||
body.rate_limit_up_bps.is_some() || body.rate_limit_down_bps.is_some();
|
||||
let touches_user_max_unique_ips = body.max_unique_ips.is_some();
|
||||
|
||||
if !is_valid_username(&body.username) {
|
||||
@@ -91,6 +95,15 @@ pub(super) async fn create_user(
|
||||
.user_data_quota
|
||||
.insert(body.username.clone(), quota);
|
||||
}
|
||||
if touches_user_rate_limits {
|
||||
cfg.access.user_rate_limits.insert(
|
||||
body.username.clone(),
|
||||
RateLimitBps {
|
||||
up_bps: body.rate_limit_up_bps.unwrap_or(0),
|
||||
down_bps: body.rate_limit_down_bps.unwrap_or(0),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let updated_limit = body.max_unique_ips;
|
||||
if let Some(limit) = updated_limit {
|
||||
@@ -115,6 +128,9 @@ pub(super) async fn create_user(
|
||||
if touches_user_data_quota {
|
||||
touched_sections.push(AccessSection::UserDataQuota);
|
||||
}
|
||||
if touches_user_rate_limits {
|
||||
touched_sections.push(AccessSection::UserRateLimits);
|
||||
}
|
||||
if touches_user_max_unique_ips {
|
||||
touched_sections.push(AccessSection::UserMaxUniqueIps);
|
||||
}
|
||||
@@ -157,6 +173,8 @@ pub(super) async fn create_user(
|
||||
.then_some(cfg.access.user_max_tcp_conns_global_each)),
|
||||
expiration_rfc3339: None,
|
||||
data_quota_bytes: None,
|
||||
rate_limit_up_bps: body.rate_limit_up_bps.filter(|limit| *limit > 0),
|
||||
rate_limit_down_bps: body.rate_limit_down_bps.filter(|limit| *limit > 0),
|
||||
max_unique_ips: updated_limit,
|
||||
current_connections: 0,
|
||||
active_unique_ips: 0,
|
||||
@@ -181,6 +199,8 @@ pub(super) async fn patch_user(
|
||||
let touches_user_max_tcp_conns = !matches!(&body.max_tcp_conns, Patch::Unchanged);
|
||||
let touches_user_expirations = !matches!(&body.expiration_rfc3339, Patch::Unchanged);
|
||||
let touches_user_data_quota = !matches!(&body.data_quota_bytes, Patch::Unchanged);
|
||||
let touches_user_rate_limits = !matches!(&body.rate_limit_up_bps, Patch::Unchanged)
|
||||
|| !matches!(&body.rate_limit_down_bps, Patch::Unchanged);
|
||||
let touches_user_max_unique_ips = !matches!(&body.max_unique_ips, Patch::Unchanged);
|
||||
|
||||
if let Some(secret) = body.secret.as_ref()
|
||||
@@ -253,6 +273,31 @@ pub(super) async fn patch_user(
|
||||
cfg.access.user_data_quota.insert(user.to_string(), quota);
|
||||
}
|
||||
}
|
||||
if touches_user_rate_limits {
|
||||
let mut rate_limit = cfg
|
||||
.access
|
||||
.user_rate_limits
|
||||
.get(user)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
match body.rate_limit_up_bps {
|
||||
Patch::Unchanged => {}
|
||||
Patch::Remove => rate_limit.up_bps = 0,
|
||||
Patch::Set(limit) => rate_limit.up_bps = limit,
|
||||
}
|
||||
match body.rate_limit_down_bps {
|
||||
Patch::Unchanged => {}
|
||||
Patch::Remove => rate_limit.down_bps = 0,
|
||||
Patch::Set(limit) => rate_limit.down_bps = limit,
|
||||
}
|
||||
if rate_limit.up_bps == 0 && rate_limit.down_bps == 0 {
|
||||
cfg.access.user_rate_limits.remove(user);
|
||||
} else {
|
||||
cfg.access
|
||||
.user_rate_limits
|
||||
.insert(user.to_string(), rate_limit);
|
||||
}
|
||||
}
|
||||
// Capture how the per-user IP limit changed, so the in-memory ip_tracker
|
||||
// can be synced (set or removed) after the config is persisted.
|
||||
let max_unique_ips_change = match body.max_unique_ips {
|
||||
@@ -288,6 +333,9 @@ pub(super) async fn patch_user(
|
||||
if touches_user_data_quota {
|
||||
touched_sections.push(AccessSection::UserDataQuota);
|
||||
}
|
||||
if touches_user_rate_limits {
|
||||
touched_sections.push(AccessSection::UserRateLimits);
|
||||
}
|
||||
if touches_user_max_unique_ips {
|
||||
touched_sections.push(AccessSection::UserMaxUniqueIps);
|
||||
}
|
||||
@@ -355,6 +403,7 @@ pub(super) async fn rotate_secret(
|
||||
AccessSection::UserMaxTcpConns,
|
||||
AccessSection::UserExpirations,
|
||||
AccessSection::UserDataQuota,
|
||||
AccessSection::UserRateLimits,
|
||||
AccessSection::UserMaxUniqueIps,
|
||||
];
|
||||
let revision =
|
||||
@@ -414,6 +463,7 @@ pub(super) async fn delete_user(
|
||||
cfg.access.user_max_tcp_conns.remove(user);
|
||||
cfg.access.user_expirations.remove(user);
|
||||
cfg.access.user_data_quota.remove(user);
|
||||
cfg.access.user_rate_limits.remove(user);
|
||||
cfg.access.user_max_unique_ips.remove(user);
|
||||
|
||||
cfg.validate()
|
||||
@@ -424,6 +474,7 @@ pub(super) async fn delete_user(
|
||||
AccessSection::UserMaxTcpConns,
|
||||
AccessSection::UserExpirations,
|
||||
AccessSection::UserDataQuota,
|
||||
AccessSection::UserRateLimits,
|
||||
AccessSection::UserMaxUniqueIps,
|
||||
];
|
||||
let revision =
|
||||
@@ -485,6 +536,18 @@ pub(super) async fn users_from_config(
|
||||
.get(&username)
|
||||
.map(chrono::DateTime::<chrono::Utc>::to_rfc3339),
|
||||
data_quota_bytes: cfg.access.user_data_quota.get(&username).copied(),
|
||||
rate_limit_up_bps: cfg
|
||||
.access
|
||||
.user_rate_limits
|
||||
.get(&username)
|
||||
.map(|limit| limit.up_bps)
|
||||
.filter(|limit| *limit > 0),
|
||||
rate_limit_down_bps: cfg
|
||||
.access
|
||||
.user_rate_limits
|
||||
.get(&username)
|
||||
.map(|limit| limit.down_bps)
|
||||
.filter(|limit| *limit > 0),
|
||||
max_unique_ips: cfg
|
||||
.access
|
||||
.user_max_unique_ips
|
||||
@@ -506,6 +569,33 @@ pub(super) async fn users_from_config(
|
||||
users
|
||||
}
|
||||
|
||||
pub(super) fn build_user_quota_list(cfg: &ProxyConfig, stats: &Stats) -> UserQuotaListData {
|
||||
let mut names = cfg.access.users.keys().cloned().collect::<Vec<_>>();
|
||||
names.sort();
|
||||
|
||||
let snapshot = stats.user_quota_snapshot();
|
||||
let mut users = Vec::with_capacity(names.len());
|
||||
for username in names {
|
||||
let Some(&data_quota_bytes) = cfg.access.user_data_quota.get(&username) else {
|
||||
continue;
|
||||
};
|
||||
if data_quota_bytes == 0 {
|
||||
continue;
|
||||
}
|
||||
let (used_bytes, last_reset_epoch_secs) = snapshot
|
||||
.get(&username)
|
||||
.map(|entry| (entry.used_bytes, entry.last_reset_epoch_secs))
|
||||
.unwrap_or((0, 0));
|
||||
users.push(UserQuotaEntry {
|
||||
username,
|
||||
data_quota_bytes,
|
||||
used_bytes,
|
||||
last_reset_epoch_secs,
|
||||
});
|
||||
}
|
||||
UserQuotaListData { users }
|
||||
}
|
||||
|
||||
fn empty_user_links() -> UserLinks {
|
||||
UserLinks {
|
||||
classic: Vec::new(),
|
||||
@@ -758,6 +848,34 @@ mod tests {
|
||||
assert_eq!(alice.max_tcp_conns, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn users_from_config_reports_user_rate_limits() {
|
||||
let mut cfg = ProxyConfig::default();
|
||||
cfg.access.users.insert(
|
||||
"alice".to_string(),
|
||||
"0123456789abcdef0123456789abcdef".to_string(),
|
||||
);
|
||||
cfg.access.user_rate_limits.insert(
|
||||
"alice".to_string(),
|
||||
RateLimitBps {
|
||||
up_bps: 1024,
|
||||
down_bps: 0,
|
||||
},
|
||||
);
|
||||
|
||||
let stats = Stats::new();
|
||||
let tracker = UserIpTracker::new();
|
||||
|
||||
let users = users_from_config(&cfg, &stats, &tracker, None, None, None).await;
|
||||
let alice = users
|
||||
.iter()
|
||||
.find(|entry| entry.username == "alice")
|
||||
.expect("alice must be present");
|
||||
|
||||
assert_eq!(alice.rate_limit_up_bps, Some(1024));
|
||||
assert_eq!(alice.rate_limit_down_bps, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn users_from_config_marks_runtime_membership_when_snapshot_is_provided() {
|
||||
let mut disk_cfg = ProxyConfig::default();
|
||||
@@ -869,4 +987,68 @@ mod tests {
|
||||
.any(|entry| entry.domain == "front-a.example.com")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_user_quota_list_skips_users_without_positive_quota_and_sorts_by_username() {
|
||||
let mut cfg = ProxyConfig::default();
|
||||
cfg.access.users.insert(
|
||||
"alice".to_string(),
|
||||
"0123456789abcdef0123456789abcdef".to_string(),
|
||||
);
|
||||
cfg.access.users.insert(
|
||||
"bob".to_string(),
|
||||
"fedcba9876543210fedcba9876543210".to_string(),
|
||||
);
|
||||
cfg.access.users.insert(
|
||||
"carol".to_string(),
|
||||
"aaaabbbbccccddddeeeeffff00001111".to_string(),
|
||||
);
|
||||
// alice has a positive quota and should be listed.
|
||||
cfg.access
|
||||
.user_data_quota
|
||||
.insert("alice".to_string(), 1 << 20);
|
||||
// bob has no quota entry at all (None) — should be skipped.
|
||||
// carol has an explicit zero quota — should be skipped.
|
||||
cfg.access.user_data_quota.insert("carol".to_string(), 0);
|
||||
|
||||
let stats = Stats::new();
|
||||
// Charge some traffic against alice; carol gets traffic too but should
|
||||
// still be filtered out by the quota check.
|
||||
let alice_stats = stats.get_or_create_user_stats_handle("alice");
|
||||
stats.quota_charge_post_write(&alice_stats, 4096);
|
||||
let carol_stats = stats.get_or_create_user_stats_handle("carol");
|
||||
stats.quota_charge_post_write(&carol_stats, 99);
|
||||
|
||||
let data = build_user_quota_list(&cfg, &stats);
|
||||
|
||||
assert_eq!(data.users.len(), 1);
|
||||
let entry = &data.users[0];
|
||||
assert_eq!(entry.username, "alice");
|
||||
assert_eq!(entry.data_quota_bytes, 1 << 20);
|
||||
assert_eq!(entry.used_bytes, 4096);
|
||||
assert_eq!(entry.last_reset_epoch_secs, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_user_quota_list_orders_multiple_users_by_username_ascending() {
|
||||
let mut cfg = ProxyConfig::default();
|
||||
for name in ["charlie", "alice", "bob"] {
|
||||
cfg.access.users.insert(
|
||||
name.to_string(),
|
||||
"0123456789abcdef0123456789abcdef".to_string(),
|
||||
);
|
||||
cfg.access.user_data_quota.insert(name.to_string(), 1 << 30);
|
||||
}
|
||||
|
||||
let stats = Stats::new();
|
||||
let data = build_user_quota_list(&cfg, &stats);
|
||||
|
||||
let names: Vec<&str> = data.users.iter().map(|e| e.username.as_str()).collect();
|
||||
assert_eq!(names, vec!["alice", "bob", "charlie"]);
|
||||
for entry in &data.users {
|
||||
assert_eq!(entry.used_bytes, 0);
|
||||
assert_eq!(entry.last_reset_epoch_secs, 0);
|
||||
assert_eq!(entry.data_quota_bytes, 1 << 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user