mirror of
https://github.com/telemt/telemt.git
synced 2026-05-20 01:45:48 +03:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4c33eff39 | ||
|
|
01b0c5c6ce | ||
|
|
ad1bb5cc1a | ||
|
|
08cde1a255 | ||
|
|
faf1f28f9d | ||
|
|
32613c8e68 | ||
|
|
1fe621f743 | ||
|
|
3b0ebf3c9e | ||
|
|
b41f6bc21e | ||
|
|
0a9f599611 | ||
|
|
cdb021fc71 | ||
|
|
6b61183b9d |
@@ -52,6 +52,10 @@ 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
|
||||
|
||||
---
|
||||
@@ -79,4 +83,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,8 +205,6 @@ 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`
|
||||
@@ -217,8 +215,6 @@ 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
|
||||
@@ -307,7 +303,7 @@ An empty request body is accepted and generates a new secret automatically.
|
||||
| `route_mode` | `string` | Current route mode label from route runtime controller. |
|
||||
| `reroute_active` | `bool` | `true` when ME fallback currently routes new sessions to Direct-DC. |
|
||||
| `reroute_to_direct_at_epoch_secs` | `u64?` | Unix timestamp when current direct reroute began. |
|
||||
| `reroute_reason` | `string?` | `startup_direct_fallback`, `fast_not_ready_fallback`, or `strict_grace_fallback` while reroute is active. |
|
||||
| `reroute_reason` | `string?` | `fast_not_ready_fallback` or `strict_grace_fallback` while reroute is active. |
|
||||
| `startup_status` | `string` | Startup status (`pending`, `initializing`, `ready`, `failed`, `skipped`). |
|
||||
| `startup_stage` | `string` | Current startup stage identifier. |
|
||||
| `startup_progress_pct` | `f64` | Startup progress percentage (`0..100`). |
|
||||
@@ -1170,8 +1166,6 @@ 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. |
|
||||
@@ -1248,12 +1242,6 @@ 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.
|
||||
@@ -1286,12 +1274,12 @@ Additional runtime endpoint behavior:
|
||||
## ME Fallback Behavior Exposed Via API
|
||||
|
||||
When `general.use_middle_proxy=true` and `general.me2dc_fallback=true`:
|
||||
- Startup opens Direct-DC routing first, then initializes ME in background and switches new sessions to Middle mode after ME readiness is observed.
|
||||
- Startup does not block on full ME pool readiness; initialization can continue in background.
|
||||
- Runtime initialization payload can expose ME stage `background_init` until pool becomes ready.
|
||||
- Admission/routing decision uses two readiness grace windows for "ME not ready" periods:
|
||||
direct startup fallback before first-ever readiness is observed,
|
||||
`80s` before first-ever readiness is observed (startup grace),
|
||||
`6s` after readiness has been observed at least once (runtime failover timeout).
|
||||
- While fallback is active, new sessions are routed via Direct-DC; when ME becomes ready, routing returns to Middle mode. Direct sessions affected by the cutover are closed with the existing staggered delay so clients reconnect through the current route.
|
||||
- While in fallback window breach, new sessions are routed via Direct-DC; when ME becomes ready, routing returns to Middle mode for new sessions.
|
||||
|
||||
## Serialization Rules
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ 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)
|
||||
@@ -29,12 +31,16 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
|
||||
# Top-level keys
|
||||
|
||||
| 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) |
|
||||
| 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"` | `✘` |
|
||||
|
||||
## 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.
|
||||
@@ -79,145 +85,151 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
|
||||
# [general]
|
||||
|
||||
| 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` | `true` |
|
||||
| [`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"` |
|
||||
| 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"` | `✘` |
|
||||
|
||||
## data_path
|
||||
- **Constraints / validation**: `String` (optional).
|
||||
@@ -228,6 +240,24 @@ 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`.
|
||||
@@ -392,7 +422,7 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
```
|
||||
## me2dc_fallback
|
||||
- **Constraints / validation**: `bool`.
|
||||
- **Description**: Allows Direct-DC fallback when ME is unavailable. With `use_middle_proxy = true`, startup opens Direct-DC routing first and moves new sessions to ME after ME readiness is observed.
|
||||
- **Description**: Allows fallback from ME mode to direct DC when ME startup fails.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
@@ -401,14 +431,14 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
```
|
||||
## me2dc_fast
|
||||
- **Constraints / validation**: `bool`. Active only when `use_middle_proxy = true` and `me2dc_fallback = true`.
|
||||
- **Description**: Fast ME->Direct fallback mode for new sessions after ME was ready at least once. Initial direct-first startup fallback is controlled by `me2dc_fallback`.
|
||||
- **Description**: Fast ME->Direct fallback mode for new sessions.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
use_middle_proxy = true
|
||||
me2dc_fallback = true
|
||||
me2dc_fast = true
|
||||
me2dc_fast = false
|
||||
```
|
||||
## me_keepalive_enabled
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -905,6 +935,15 @@ 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.
|
||||
@@ -1520,11 +1559,11 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [general.modes]
|
||||
|
||||
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`classic`](#classic) | `bool` | `false` |
|
||||
| [`secure`](#secure) | `bool` | `false` |
|
||||
| [`tls`](#tls) | `bool` | `true` |
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`classic`](#classic) | `bool` | `false` | `✘` |
|
||||
| [`secure`](#secure) | `bool` | `false` | `✘` |
|
||||
| [`tls`](#tls) | `bool` | `true` | `✘` |
|
||||
|
||||
## classic
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -1558,11 +1597,11 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [general.links]
|
||||
|
||||
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` |
|
||||
| [`public_host`](#public_host) | `String` | — |
|
||||
| [`public_port`](#public_port) | `u16` | — |
|
||||
| Key | Type | Default | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`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".
|
||||
@@ -1598,11 +1637,11 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [general.telemetry]
|
||||
|
||||
|
||||
| Key | Type | Default |
|
||||
| --- | ---- | ------- |
|
||||
| [`core_enabled`](#core_enabled) | `bool` | `true` |
|
||||
| [`user_enabled`](#user_enabled) | `bool` | `true` |
|
||||
| [`me_level`](#me_level) | `"silent"`, `"normal"`, or `"debug"` | `"normal"` |
|
||||
| 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"` | `✔` |
|
||||
|
||||
## core_enabled
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -1636,18 +1675,18 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [network]
|
||||
|
||||
|
||||
| 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[]` | `[]` |
|
||||
| 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[]` | `[]` | `✔` |
|
||||
|
||||
## ipv4
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -1757,23 +1796,27 @@ This document lists all configuration keys accepted by `config.toml`.
|
||||
# [server]
|
||||
|
||||
|
||||
| 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` |
|
||||
| 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 | `✘` |
|
||||
|
||||
## port
|
||||
- **Constraints / validation**: `u16`.
|
||||
@@ -1930,16 +1973,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 |
|
||||
| --- | ---- | ------- |
|
||||
| [`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 | 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
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -2021,21 +2064,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 |
|
||||
| --- | ---- | ------- |
|
||||
| [`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 | 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
|
||||
- **Constraints / validation**: `bool`.
|
||||
@@ -2159,13 +2202,14 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
# [[server.listeners]]
|
||||
|
||||
|
||||
| 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` |
|
||||
| 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` | `✘` |
|
||||
|
||||
## ip
|
||||
- **Constraints / validation**: Required field. Must be an `IpAddr`.
|
||||
@@ -2176,6 +2220,16 @@ 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`.
|
||||
@@ -2209,8 +2263,7 @@ 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**:
|
||||
@@ -2225,18 +2278,18 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
# [timeouts]
|
||||
|
||||
|
||||
| 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` |
|
||||
| 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` | `✘` |
|
||||
|
||||
## 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`).
|
||||
@@ -2292,15 +2345,6 @@ 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).
|
||||
@@ -2342,41 +2386,40 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
# [censorship]
|
||||
|
||||
|
||||
| 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` |
|
||||
| [`exclusive_mask`](#exclusive_mask) | `Map<String,String>` | `{}` |
|
||||
| [`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 | 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
|
||||
- **Constraints / validation**: Must be a non-empty domain name. Must not contain spaces or `/`.
|
||||
@@ -2460,18 +2503,6 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
|
||||
[censorship]
|
||||
mask_port = 443
|
||||
```
|
||||
## exclusive_mask
|
||||
- **Constraints / validation**: TOML map. Keys must be SNI domain names. Values must be `host:port` with `port > 0`; IPv6 literals must be bracketed.
|
||||
- **Description**: Per-SNI TCP mask targets for fallback traffic. When a TLS ClientHello SNI matches a key, Telemt relays that unauthenticated connection to the mapped target. Other fallback traffic keeps using the existing `mask_host`/`mask_port` or SNI-aware default masking behavior.
|
||||
- **Example**:
|
||||
|
||||
```toml
|
||||
[censorship]
|
||||
tls_domains = ["petrovich.ru", "bsi.bund.de", "telekom.com"]
|
||||
|
||||
[censorship.exclusive_mask]
|
||||
"bsi.bund.de" = "127.0.0.1:443"
|
||||
```
|
||||
## mask_unix_sock
|
||||
- **Constraints / validation**: `String` (optional).
|
||||
- Must not be empty when set.
|
||||
@@ -2810,15 +2841,15 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
# [censorship.tls_fetch]
|
||||
|
||||
|
||||
| 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` |
|
||||
| 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` | `✘` |
|
||||
|
||||
## profiles
|
||||
- **Constraints / validation**: `String[]`. Empty list falls back to defaults; values are deduplicated preserving order.
|
||||
@@ -2887,24 +2918,24 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
# [access]
|
||||
|
||||
|
||||
| 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>` | `{}` |
|
||||
| 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>` | `{}` | `✔` |
|
||||
|
||||
## users
|
||||
- **Constraints / validation**: Must not be empty (at least one user must exist). Each value must be **exactly 32 hex characters**.
|
||||
@@ -3068,19 +3099,23 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
|
||||
# [[upstreams]]
|
||||
|
||||
|
||||
| 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` | — |
|
||||
| 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` | — | `✘` |
|
||||
|
||||
## type
|
||||
- **Constraints / validation**: Required field. Must be one of: `"direct"`, `"socks4"`, `"socks5"`, `"shadowsocks"`.
|
||||
@@ -3131,6 +3166,26 @@ 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).
|
||||
@@ -3161,6 +3216,26 @@ 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,6 +10,8 @@
|
||||
>
|
||||
> Параметры конфигурации, подробно описанные в этом документе, предназначены для опытных пользователей и для целей тонкой настройки. Изменение этих параметров без четкого понимания их функции может привести к нестабильности приложения или другому неожиданному поведению. Пожалуйста, действуйте осторожно и на свой страх и риск.
|
||||
|
||||
> `Hot-Reload` показывает, применяет ли config watcher изменение без перезапуска процесса; `✘` означает, что для runtime-эффекта нужен перезапуск.
|
||||
|
||||
# Содержание
|
||||
- [Ключи верхнего уровня](#top-level-keys)
|
||||
- [general](#general)
|
||||
@@ -29,12 +31,16 @@
|
||||
|
||||
# Ключи верхнего уровня
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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 маршрутизации) |
|
||||
| Ключ | Тип | По умолчанию | 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 = "path/to/file.toml"`. Значения параметра обрабатываются перед анализом TOML. Максимальное количество - 10.
|
||||
@@ -79,145 +85,151 @@
|
||||
|
||||
# [general]
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` | `true` |
|
||||
| [`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"` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `String` (необязательный параметр).
|
||||
@@ -228,6 +240,24 @@
|
||||
[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`.
|
||||
@@ -392,7 +422,7 @@
|
||||
```
|
||||
## me2dc_fallback
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
- **Описание**: Разрешает fallback на прямой DC, когда ME недоступен. При `use_middle_proxy = true` запуск сначала открывает маршрутизацию через Direct-DC, а новые сеансы переводятся на ME после подтверждения готовности ME.
|
||||
- **Описание**: Перейти из режима ME в режим прямого соединения (DC) в случае сбоя запуска ME.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
@@ -401,14 +431,14 @@
|
||||
```
|
||||
## me2dc_fast
|
||||
- **Ограничения / валидация**: `bool`. Используется только, когда `use_middle_proxy = true` и `me2dc_fallback = true`.
|
||||
- **Описание**: Быстрый fallback ME->Direct для новых сеансов после того, как ME уже был готов хотя бы один раз. Начальный direct-first fallback управляется `me2dc_fallback`.
|
||||
- **Описание**: Режим для быстрого перехода между режимами ME->DC для новых сеансов.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
use_middle_proxy = true
|
||||
me2dc_fallback = true
|
||||
me2dc_fast = true
|
||||
me2dc_fast = false
|
||||
```
|
||||
## me_keepalive_enabled
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -905,6 +935,15 @@
|
||||
[general]
|
||||
upstream_connect_budget_ms = 3000
|
||||
```
|
||||
## tg_connect
|
||||
- **Ограничения / валидация**: Должно быть `> 0` (секунды).
|
||||
- **Описание**: Таймаут подключения к upstream-серверам Telegram.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[general]
|
||||
tg_connect = 10
|
||||
```
|
||||
## upstream_unhealthy_fail_threshold
|
||||
- **Ограничения / валидация**: Должно быть `> 0`.
|
||||
- **Описание**: Количество неудачных запросов подряд, после которого upstream помечается, как неработоспособный.
|
||||
@@ -1522,11 +1561,11 @@
|
||||
# [general.modes]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`classic`](#classic) | `bool` | `false` |
|
||||
| [`secure`](#secure) | `bool` | `false` |
|
||||
| [`tls`](#tls) | `bool` | `true` |
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`classic`](#classic) | `bool` | `false` | `✘` |
|
||||
| [`secure`](#secure) | `bool` | `false` | `✘` |
|
||||
| [`tls`](#tls) | `bool` | `true` | `✘` |
|
||||
|
||||
## classic
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -1560,11 +1599,11 @@
|
||||
# [general.links]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` |
|
||||
| [`public_host`](#public_host) | `String` | — |
|
||||
| [`public_port`](#public_port) | `u16` | — |
|
||||
| Ключ | Тип | По умолчанию | Hot-Reload |
|
||||
| --- | ---- | ------- | ---------- |
|
||||
| [`show`](#show) | `"*"` or `String[]` | `"*"` | `✘` |
|
||||
| [`public_host`](#public_host) | `String` | — | `✘` |
|
||||
| [`public_port`](#public_port) | `u16` | — | `✘` |
|
||||
|
||||
## show
|
||||
- **Ограничения / валидация**: `"*"` или `String[]`. Пустое значение означает, что нельзя показывать никому.
|
||||
@@ -1600,11 +1639,11 @@
|
||||
# [general.telemetry]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`core_enabled`](#core_enabled) | `bool` | `true` |
|
||||
| [`user_enabled`](#user_enabled) | `bool` | `true` |
|
||||
| [`me_level`](#me_level) | `"silent"`, `"normal"`, or `"debug"` | `"normal"` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -1638,18 +1677,18 @@
|
||||
# [network]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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[]` | `[]` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -1759,23 +1798,27 @@
|
||||
# [server]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `u16`.
|
||||
@@ -1931,16 +1974,16 @@
|
||||
Примечание. Рабочий процесс `conntrack-control` работает **только в Linux**. В других операционных системах не запускается; если inline_conntrack_control имеет значение `true`, в логи записывается предупреждение. Для эффективной работы также требуется **CAP_NET_ADMIN** и пригодный к использованию бэкенд (nft или iptables/ip6tables в PATH). Утилита `conntrack` используется для удаления необязательных записей таблицы под нагрузкой.
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -2027,21 +2070,21 @@
|
||||
Примечание: В этом разделе также задается устаревший параметр `[server.admin_api]` (аналогично `[server.api]`).
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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"` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
@@ -2165,13 +2208,14 @@
|
||||
# [[server.listeners]]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`ip`](#ip) | `IpAddr` | — |
|
||||
| [`announce`](#announce) | `String` | — |
|
||||
| [`announce_ip`](#announce_ip) | `IpAddr` | — |
|
||||
| [`proxy_protocol`](#proxy_protocol) | `bool` | — |
|
||||
| [`reuse_allow`](#reuse_allow) | `bool` | `false` |
|
||||
| Ключ | Тип | По умолчанию | 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-адрес в формате строки.
|
||||
@@ -2182,6 +2226,16 @@
|
||||
[[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`.
|
||||
@@ -2215,8 +2269,7 @@
|
||||
ip = "0.0.0.0"
|
||||
proxy_protocol = true
|
||||
```
|
||||
## reuse_allow"
|
||||
- `reuse_allow`
|
||||
## reuse_allow
|
||||
- **Ограничения / валидация**: `bool`.
|
||||
- **Описание**: Включает `SO_REUSEPORT` для совместного использования привязки нескольких экземпляров (позволяет нескольким экземплярам telemt прослушивать один и тот же `ip:port`).
|
||||
- **Пример**:
|
||||
@@ -2231,18 +2284,18 @@
|
||||
# [timeouts]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: Должно быть `> 0`. Значение указано в секундах. Также используется в качестве верхней границы некоторых задержек эмуляции TLS (см. `censorship.server_hello_delay_max_ms`).
|
||||
@@ -2298,15 +2351,6 @@
|
||||
[timeouts]
|
||||
relay_idle_grace_after_downstream_activity_secs = 30
|
||||
```
|
||||
## tg_connect
|
||||
- **Ограничения / валидация**: `u64` (секунд).
|
||||
- **Описание**: Таймаут подключения к upstream-серверу Telegram (в секундах).
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[timeouts]
|
||||
tg_connect = 10
|
||||
```
|
||||
## client_keepalive
|
||||
- **Ограничения / валидация**: `u64` (секунд).
|
||||
- **Описание**: Таймаут keepalive для клиента..
|
||||
@@ -2348,41 +2392,40 @@
|
||||
# [censorship]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` |
|
||||
| [`exclusive_mask`](#exclusive_mask) | `Map<String,String>` | `{}` |
|
||||
| [`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` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: Не должно быть пустым. Не должно содержать пробелы или `/`.
|
||||
@@ -2465,18 +2508,6 @@
|
||||
[censorship]
|
||||
mask_port = 443
|
||||
```
|
||||
## exclusive_mask
|
||||
- **Ограничения / валидация**: TOML map. Ключи должны быть доменами SNI. Значения должны иметь формат `host:port`, где `port > 0`; IPv6 literals должны быть в квадратных скобках.
|
||||
- **Описание**: Per-SNI TCP targets для fallback-трафика. Если SNI в TLS ClientHello совпадает с ключом, Telemt проксирует это неаутентифицированное соединение на указанный target. Остальной fallback-трафик продолжает использовать существующий `mask_host`/`mask_port` или SNI-aware default masking behavior.
|
||||
- **Пример**:
|
||||
|
||||
```toml
|
||||
[censorship]
|
||||
tls_domains = ["petrovich.ru", "bsi.bund.de", "telekom.com"]
|
||||
|
||||
[censorship.exclusive_mask]
|
||||
"bsi.bund.de" = "127.0.0.1:443"
|
||||
```
|
||||
## mask_unix_sock
|
||||
- **Ограничения / валидация**: `String` (optional).
|
||||
- Значение не должно быть пустым, если задан.
|
||||
@@ -2817,15 +2848,15 @@
|
||||
# [censorship.tls_fetch]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: `String[]`. Пустой список возвращает значения по умолчанию; дубликаты удаляются с сохранением порядка.
|
||||
@@ -2894,23 +2925,24 @@
|
||||
# [access]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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>` | `{}` |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: Не должно быть пустым (должен существовать хотя бы один пользователь). Каждое значение должно состоять **ровно из 32 шестнадцатеричных символов**.
|
||||
@@ -3010,6 +3042,20 @@
|
||||
[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).
|
||||
@@ -3060,19 +3106,23 @@
|
||||
# [[upstreams]]
|
||||
|
||||
|
||||
| Ключ | Тип | По умолчанию |
|
||||
| --- | ---- | ------- |
|
||||
| [`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` | — |
|
||||
| Ключ | Тип | По умолчанию | 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
|
||||
- **Ограничения / валидация**: Обязательный параметр.`"direct"`, `"socks4"`, `"socks5"`, `"shadowsocks"`.
|
||||
@@ -3123,6 +3173,26 @@
|
||||
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).
|
||||
@@ -3153,6 +3223,26 @@
|
||||
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,19 +254,6 @@ 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,6 +105,7 @@ 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."
|
||||
@@ -180,6 +181,7 @@ 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
|
||||
}
|
||||
@@ -392,7 +394,7 @@ verify_common() {
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
if [ "${USER:-}" != "root" ] && [ "${LOGNAME:-}" != "root" ]; then
|
||||
die "$L_ERR_INCORR_ROOT_LOGIN"
|
||||
fi
|
||||
else
|
||||
@@ -539,7 +541,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
|
||||
@@ -609,33 +611,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"
|
||||
|
||||
@@ -785,11 +787,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
|
||||
@@ -916,7 +918,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')"
|
||||
@@ -927,6 +929,15 @@ 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, RateLimitBps};
|
||||
use crate::config::ProxyConfig;
|
||||
|
||||
use super::model::ApiFailure;
|
||||
|
||||
@@ -18,7 +18,6 @@ pub(super) enum AccessSection {
|
||||
UserMaxTcpConns,
|
||||
UserExpirations,
|
||||
UserDataQuota,
|
||||
UserRateLimits,
|
||||
UserMaxUniqueIps,
|
||||
}
|
||||
|
||||
@@ -30,7 +29,6 @@ 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",
|
||||
}
|
||||
}
|
||||
@@ -171,15 +169,6 @@ 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
|
||||
@@ -208,7 +197,6 @@ 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(),
|
||||
}
|
||||
}
|
||||
@@ -218,28 +206,6 @@ 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());
|
||||
@@ -319,26 +285,3 @@ 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,9 +68,7 @@ use runtime_zero::{
|
||||
build_limits_effective_data, build_runtime_gates_data, build_security_posture_data,
|
||||
build_system_info_data,
|
||||
};
|
||||
use users::{
|
||||
build_user_quota_list, create_user, delete_user, patch_user, rotate_secret, users_from_config,
|
||||
};
|
||||
use users::{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);
|
||||
@@ -506,12 +504,6 @@ 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,8 +473,6 @@ 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,
|
||||
@@ -510,19 +508,6 @@ 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,
|
||||
@@ -531,8 +516,6 @@ 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>,
|
||||
}
|
||||
|
||||
@@ -548,10 +531,6 @@ 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,9 +114,7 @@ mod tests {
|
||||
"secret": "00112233445566778899aabbccddeeff",
|
||||
"max_tcp_conns": 0,
|
||||
"max_unique_ips": null,
|
||||
"data_quota_bytes": 1024,
|
||||
"rate_limit_up_bps": 4096,
|
||||
"rate_limit_down_bps": null
|
||||
"data_quota_bytes": 1024
|
||||
}"#;
|
||||
let req: PatchUserRequest = serde_json::from_str(raw).expect("valid json");
|
||||
assert_eq!(
|
||||
@@ -126,8 +124,6 @@ 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));
|
||||
}
|
||||
|
||||
@@ -178,7 +178,6 @@ pub(super) async fn build_runtime_gates_data(
|
||||
cfg: &ProxyConfig,
|
||||
) -> RuntimeGatesData {
|
||||
let startup_summary = build_runtime_startup_summary(shared).await;
|
||||
let startup_snapshot = shared.startup_tracker.snapshot().await;
|
||||
let route_state = shared.route_runtime.snapshot();
|
||||
let route_mode = route_state.mode.as_str();
|
||||
let fast_fallback_enabled =
|
||||
@@ -192,9 +191,7 @@ pub(super) async fn build_runtime_gates_data(
|
||||
None
|
||||
};
|
||||
let reroute_reason = if reroute_active {
|
||||
if startup_snapshot.me.status.as_str() != "ready" {
|
||||
Some("startup_direct_fallback")
|
||||
} else if fast_fallback_enabled {
|
||||
if fast_fallback_enabled {
|
||||
Some("fast_not_ready_fallback")
|
||||
} else {
|
||||
Some("strict_grace_fallback")
|
||||
|
||||
186
src/api/users.rs
186
src/api/users.rs
@@ -3,7 +3,6 @@ use std::net::IpAddr;
|
||||
use hyper::StatusCode;
|
||||
|
||||
use crate::config::ProxyConfig;
|
||||
use crate::config::RateLimitBps;
|
||||
use crate::ip_tracker::UserIpTracker;
|
||||
use crate::stats::Stats;
|
||||
|
||||
@@ -14,9 +13,8 @@ use super::config_store::{
|
||||
};
|
||||
use super::model::{
|
||||
ApiFailure, CreateUserRequest, CreateUserResponse, PatchUserRequest, RotateSecretRequest,
|
||||
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,
|
||||
TlsDomainLink, UserInfo, UserLinks, is_valid_ad_tag, is_valid_user_secret, is_valid_username,
|
||||
parse_optional_expiration, parse_patch_expiration, random_user_secret,
|
||||
};
|
||||
use super::patch::Patch;
|
||||
|
||||
@@ -29,8 +27,6 @@ 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) {
|
||||
@@ -95,15 +91,6 @@ 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 {
|
||||
@@ -128,9 +115,6 @@ 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);
|
||||
}
|
||||
@@ -173,8 +157,6 @@ 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,
|
||||
@@ -199,8 +181,6 @@ 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()
|
||||
@@ -273,31 +253,6 @@ 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 {
|
||||
@@ -333,9 +288,6 @@ 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);
|
||||
}
|
||||
@@ -403,7 +355,6 @@ pub(super) async fn rotate_secret(
|
||||
AccessSection::UserMaxTcpConns,
|
||||
AccessSection::UserExpirations,
|
||||
AccessSection::UserDataQuota,
|
||||
AccessSection::UserRateLimits,
|
||||
AccessSection::UserMaxUniqueIps,
|
||||
];
|
||||
let revision =
|
||||
@@ -463,7 +414,6 @@ 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()
|
||||
@@ -474,7 +424,6 @@ pub(super) async fn delete_user(
|
||||
AccessSection::UserMaxTcpConns,
|
||||
AccessSection::UserExpirations,
|
||||
AccessSection::UserDataQuota,
|
||||
AccessSection::UserRateLimits,
|
||||
AccessSection::UserMaxUniqueIps,
|
||||
];
|
||||
let revision =
|
||||
@@ -536,18 +485,6 @@ 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
|
||||
@@ -569,33 +506,6 @@ 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(),
|
||||
@@ -848,34 +758,6 @@ 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();
|
||||
@@ -987,68 +869,4 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,7 +617,6 @@ fn warn_non_hot_changes(old: &ProxyConfig, new: &ProxyConfig, non_hot_changed: b
|
||||
|| old.censorship.mask != new.censorship.mask
|
||||
|| old.censorship.mask_host != new.censorship.mask_host
|
||||
|| old.censorship.mask_port != new.censorship.mask_port
|
||||
|| old.censorship.exclusive_mask != new.censorship.exclusive_mask
|
||||
|| old.censorship.mask_unix_sock != new.censorship.mask_unix_sock
|
||||
|| old.censorship.fake_cert_len != new.censorship.fake_cert_len
|
||||
|| old.censorship.tls_emulation != new.censorship.tls_emulation
|
||||
|
||||
@@ -31,84 +31,6 @@ fn is_valid_tls_domain_name(domain: &str) -> bool {
|
||||
.any(|ch| ch.is_whitespace() || matches!(ch, '/' | '\\'))
|
||||
}
|
||||
|
||||
fn normalize_domain_to_ascii(domain: &str, field: &str) -> Result<String> {
|
||||
let domain = domain.trim();
|
||||
if !is_valid_tls_domain_name(domain) {
|
||||
return Err(ProxyError::Config(format!(
|
||||
"Invalid {field}: '{}'. Must be a valid domain name",
|
||||
domain
|
||||
)));
|
||||
}
|
||||
|
||||
let parsed = url::Url::parse(&format!("https://{domain}/")).map_err(|error| {
|
||||
ProxyError::Config(format!(
|
||||
"Invalid {field}: '{}'. IDNA conversion failed: {error}",
|
||||
domain
|
||||
))
|
||||
})?;
|
||||
let host = parsed.host_str().ok_or_else(|| {
|
||||
ProxyError::Config(format!("Invalid {field}: '{}'. Host is empty", domain))
|
||||
})?;
|
||||
Ok(host.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
fn normalize_mask_host_to_ascii(host: &str, field: &str) -> Result<String> {
|
||||
let host = host.trim();
|
||||
if host.starts_with('[') && host.ends_with(']') {
|
||||
let inner = &host[1..host.len() - 1];
|
||||
let ip = inner.parse::<std::net::IpAddr>().map_err(|_| {
|
||||
ProxyError::Config(format!("Invalid {field}: '{}'. IPv6 literal is invalid", host))
|
||||
})?;
|
||||
return match ip {
|
||||
std::net::IpAddr::V6(v6) => Ok(format!("[{v6}]")),
|
||||
std::net::IpAddr::V4(v4) => Ok(v4.to_string()),
|
||||
};
|
||||
}
|
||||
if let Ok(ip) = host.parse::<std::net::IpAddr>() {
|
||||
return match ip {
|
||||
std::net::IpAddr::V4(v4) => Ok(v4.to_string()),
|
||||
std::net::IpAddr::V6(v6) => Ok(format!("[{v6}]")),
|
||||
};
|
||||
}
|
||||
|
||||
normalize_domain_to_ascii(host, field)
|
||||
}
|
||||
|
||||
fn parse_exclusive_mask_target(target: &str) -> Option<(&str, u16)> {
|
||||
let target = target.trim();
|
||||
if target.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if target.starts_with('[') {
|
||||
let end = target.find(']')?;
|
||||
if target.get(end + 1..end + 2)? != ":" {
|
||||
return None;
|
||||
}
|
||||
let host = &target[..=end];
|
||||
let port = target[end + 2..].parse::<u16>().ok()?;
|
||||
return (port > 0).then_some((host, port));
|
||||
}
|
||||
|
||||
let (host, port) = target.rsplit_once(':')?;
|
||||
if host.is_empty() || host.contains(':') {
|
||||
return None;
|
||||
}
|
||||
let port = port.parse::<u16>().ok()?;
|
||||
(port > 0).then_some((host, port))
|
||||
}
|
||||
|
||||
fn normalize_exclusive_mask_target(target: &str, field: &str) -> Result<String> {
|
||||
let (host, port) = parse_exclusive_mask_target(target).ok_or_else(|| {
|
||||
ProxyError::Config(format!(
|
||||
"Invalid {field}: '{}'. Expected host:port with port > 0",
|
||||
target
|
||||
))
|
||||
})?;
|
||||
let host = normalize_mask_host_to_ascii(host, field)?;
|
||||
Ok(format!("{host}:{port}"))
|
||||
}
|
||||
|
||||
const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
|
||||
"general",
|
||||
"network",
|
||||
@@ -369,7 +291,6 @@ const CENSORSHIP_CONFIG_KEYS: &[&str] = &[
|
||||
"mask",
|
||||
"mask_host",
|
||||
"mask_port",
|
||||
"exclusive_mask",
|
||||
"mask_unix_sock",
|
||||
"fake_cert_len",
|
||||
"tls_emulation",
|
||||
@@ -1966,8 +1887,10 @@ impl ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
config.censorship.tls_domain =
|
||||
normalize_domain_to_ascii(&config.censorship.tls_domain, "censorship.tls_domain")?;
|
||||
// Validate tls_domain.
|
||||
if config.censorship.tls_domain.is_empty() {
|
||||
return Err(ProxyError::Config("tls_domain cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
// Validate mask_unix_sock.
|
||||
if let Some(ref sock_path) = config.censorship.mask_unix_sock {
|
||||
@@ -1995,30 +1918,11 @@ impl ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mask_host) = config.censorship.mask_host.as_mut() {
|
||||
*mask_host = normalize_mask_host_to_ascii(mask_host, "censorship.mask_host")?;
|
||||
}
|
||||
|
||||
// Default mask_host to tls_domain if not set and no unix socket configured.
|
||||
if config.censorship.mask_host.is_none() && config.censorship.mask_unix_sock.is_none() {
|
||||
config.censorship.mask_host = Some(config.censorship.tls_domain.clone());
|
||||
}
|
||||
|
||||
for (domain, target) in &config.censorship.exclusive_mask {
|
||||
if !is_valid_tls_domain_name(domain) {
|
||||
return Err(ProxyError::Config(format!(
|
||||
"Invalid censorship.exclusive_mask domain: '{}'. Must be a valid domain name",
|
||||
domain
|
||||
)));
|
||||
}
|
||||
if parse_exclusive_mask_target(target).is_none() {
|
||||
return Err(ProxyError::Config(format!(
|
||||
"Invalid censorship.exclusive_mask target for '{}': '{}'. Expected host:port with port > 0",
|
||||
domain, target
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize optional TLS fetch scope: whitespace-only values disable scoped routing.
|
||||
config.censorship.tls_fetch_scope = config.censorship.tls_fetch_scope.trim().to_string();
|
||||
|
||||
@@ -2049,11 +1953,8 @@ impl ProxyConfig {
|
||||
let mut all = Vec::with_capacity(1 + config.censorship.tls_domains.len());
|
||||
all.push(config.censorship.tls_domain.clone());
|
||||
for d in std::mem::take(&mut config.censorship.tls_domains) {
|
||||
if !d.is_empty() {
|
||||
let domain = normalize_domain_to_ascii(&d, "censorship.tls_domains entry")?;
|
||||
if !all.contains(&domain) {
|
||||
all.push(domain);
|
||||
}
|
||||
if !d.is_empty() && !all.contains(&d) {
|
||||
all.push(d);
|
||||
}
|
||||
}
|
||||
// keep primary as tls_domain; store remaining back to tls_domains
|
||||
@@ -2062,20 +1963,6 @@ impl ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
let mut exclusive_mask = HashMap::with_capacity(config.censorship.exclusive_mask.len());
|
||||
for (domain, target) in std::mem::take(&mut config.censorship.exclusive_mask) {
|
||||
let domain = normalize_domain_to_ascii(
|
||||
&domain,
|
||||
"censorship.exclusive_mask domain",
|
||||
)?;
|
||||
let target = normalize_exclusive_mask_target(
|
||||
&target,
|
||||
"censorship.exclusive_mask target",
|
||||
)?;
|
||||
exclusive_mask.insert(domain, target);
|
||||
}
|
||||
config.censorship.exclusive_mask = exclusive_mask;
|
||||
|
||||
// Migration: prefer_ipv6 -> network.prefer.
|
||||
if config.general.prefer_ipv6 {
|
||||
if config.network.prefer == 4 {
|
||||
@@ -2239,21 +2126,6 @@ impl ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
for (domain, target) in &self.censorship.exclusive_mask {
|
||||
if !is_valid_tls_domain_name(domain) {
|
||||
return Err(ProxyError::Config(format!(
|
||||
"Invalid censorship.exclusive_mask domain: '{}'. Must be a valid domain name",
|
||||
domain
|
||||
)));
|
||||
}
|
||||
if parse_exclusive_mask_target(target).is_none() {
|
||||
return Err(ProxyError::Config(format!(
|
||||
"Invalid censorship.exclusive_mask target for '{}': '{}'. Expected host:port with port > 0",
|
||||
domain, target
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
for (user, tag) in &self.access.user_ad_tags {
|
||||
let zeros = "00000000000000000000000000000000";
|
||||
if !is_valid_ad_tag(tag) {
|
||||
@@ -2795,40 +2667,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusive_mask_parses_domain_target_map() {
|
||||
let cfg = load_config_from_temp_toml(
|
||||
r#"
|
||||
[general]
|
||||
[network]
|
||||
[server]
|
||||
[access]
|
||||
[censorship]
|
||||
tls_domain = "weißbiergärten.de"
|
||||
tls_domains = ["bürgeramt.de"]
|
||||
[censorship.exclusive_mask]
|
||||
"bürgeramt.de" = "rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz.de:443"
|
||||
"ipv6.example" = "[::1]:443"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(cfg.censorship.tls_domain, "xn--weibiergrten-n9a9e.de");
|
||||
assert_eq!(
|
||||
cfg.censorship.tls_domains,
|
||||
vec!["xn--brgeramt-n4a.de".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.censorship
|
||||
.exclusive_mask
|
||||
.get("xn--brgeramt-n4a.de"),
|
||||
Some(&"xn--rindfleischetikettierungsberwachungsaufgabenbertragungsgesetz-nkgt.de:443".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.censorship.exclusive_mask.get("ipv6.example"),
|
||||
Some(&"[::1]:443".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_gray_action_parses_and_defaults_to_drop() {
|
||||
let cfg_default: ProxyConfig = toml::from_str(
|
||||
|
||||
@@ -1719,10 +1719,6 @@ pub struct AntiCensorshipConfig {
|
||||
#[serde(default = "default_mask_port")]
|
||||
pub mask_port: u16,
|
||||
|
||||
/// Per-SNI TCP mask targets. Keys are SNI domains, values are `host:port`.
|
||||
#[serde(default)]
|
||||
pub exclusive_mask: HashMap<String, String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub mask_unix_sock: Option<String>,
|
||||
|
||||
@@ -1846,7 +1842,6 @@ impl Default for AntiCensorshipConfig {
|
||||
mask: default_true(),
|
||||
mask_host: None,
|
||||
mask_port: default_mask_port(),
|
||||
exclusive_mask: HashMap::new(),
|
||||
mask_unix_sock: None,
|
||||
fake_cert_len: default_fake_cert_len(),
|
||||
tls_emulation: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use tokio::sync::{RwLock, watch};
|
||||
use tokio::sync::watch;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::config::ProxyConfig;
|
||||
@@ -14,32 +14,24 @@ const RUNTIME_FALLBACK_AFTER: Duration = Duration::from_secs(6);
|
||||
pub(crate) async fn configure_admission_gate(
|
||||
config: &Arc<ProxyConfig>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Arc<RwLock<Option<Arc<MePool>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
admission_tx: &watch::Sender<bool>,
|
||||
config_rx: watch::Receiver<Arc<ProxyConfig>>,
|
||||
me_ready_rx: watch::Receiver<u64>,
|
||||
) {
|
||||
if config.general.use_middle_proxy {
|
||||
if me_pool.is_some() || config.general.me2dc_fallback {
|
||||
let initial_pool = match me_pool.as_ref() {
|
||||
Some(pool) => Some(pool.clone()),
|
||||
None => me_pool_runtime.read().await.clone(),
|
||||
};
|
||||
let initial_ready = match initial_pool.as_ref() {
|
||||
Some(pool) => pool.admission_ready_conditional_cast().await,
|
||||
None => false,
|
||||
};
|
||||
if let Some(pool) = me_pool.as_ref() {
|
||||
let initial_ready = pool.admission_ready_conditional_cast().await;
|
||||
let mut fallback_enabled = config.general.me2dc_fallback;
|
||||
let mut fast_fallback_enabled = fallback_enabled && config.general.me2dc_fast;
|
||||
let (initial_gate_open, initial_route_mode, initial_fallback_reason) = if initial_ready
|
||||
{
|
||||
(true, RelayRouteMode::Middle, None)
|
||||
} else if fallback_enabled {
|
||||
} else if fast_fallback_enabled {
|
||||
(
|
||||
true,
|
||||
RelayRouteMode::Direct,
|
||||
Some("startup_direct_fallback"),
|
||||
Some("fast_not_ready_fallback"),
|
||||
)
|
||||
} else {
|
||||
(false, RelayRouteMode::Middle, None)
|
||||
@@ -57,8 +49,7 @@ pub(crate) async fn configure_admission_gate(
|
||||
warn!("Conditional-admission gate: closed / ME pool is NOT ready)");
|
||||
}
|
||||
|
||||
let mut pool_for_gate = initial_pool;
|
||||
let pool_runtime_for_gate = me_pool_runtime.clone();
|
||||
let pool_for_gate = pool.clone();
|
||||
let admission_tx_gate = admission_tx.clone();
|
||||
let route_runtime_gate = route_runtime.clone();
|
||||
let mut config_rx_gate = config_rx.clone();
|
||||
@@ -92,27 +83,12 @@ pub(crate) async fn configure_admission_gate(
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_millis(admission_poll_ms)) => {}
|
||||
}
|
||||
if pool_for_gate.is_none() {
|
||||
pool_for_gate = pool_runtime_for_gate.read().await.clone();
|
||||
}
|
||||
let ready = match pool_for_gate.as_ref() {
|
||||
Some(pool) => pool.admission_ready_conditional_cast().await,
|
||||
None => false,
|
||||
};
|
||||
let ready = pool_for_gate.admission_ready_conditional_cast().await;
|
||||
let now = Instant::now();
|
||||
let (next_gate_open, next_route_mode, next_fallback_reason) = if ready {
|
||||
ready_observed = true;
|
||||
not_ready_since = None;
|
||||
if let Some(pool) = pool_for_gate.as_ref() {
|
||||
pool.set_runtime_ready(true);
|
||||
}
|
||||
(true, RelayRouteMode::Middle, None)
|
||||
} else if fallback_enabled && !ready_observed {
|
||||
(
|
||||
true,
|
||||
RelayRouteMode::Direct,
|
||||
Some("startup_direct_fallback"),
|
||||
)
|
||||
} else if fast_fallback_enabled {
|
||||
(
|
||||
true,
|
||||
@@ -146,14 +122,7 @@ pub(crate) async fn configure_admission_gate(
|
||||
);
|
||||
} else {
|
||||
let fallback_reason = next_fallback_reason.unwrap_or("unknown");
|
||||
if fallback_reason == "startup_direct_fallback" {
|
||||
warn!(
|
||||
target_mode = route_mode.as_str(),
|
||||
cutover_generation = snapshot.generation,
|
||||
fallback_reason,
|
||||
"ME pool not-ready during startup; routing new sessions via Direct-DC"
|
||||
);
|
||||
} else if fallback_reason == "strict_grace_fallback" {
|
||||
if fallback_reason == "strict_grace_fallback" {
|
||||
let fallback_after = if ready_observed {
|
||||
RUNTIME_FALLBACK_AFTER
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::time::Duration;
|
||||
use tokio::net::TcpListener;
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::sync::{RwLock, Semaphore, watch};
|
||||
use tokio::sync::{Semaphore, watch};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::config::{ProxyConfig, RstOnCloseMode};
|
||||
@@ -63,7 +63,6 @@ pub(crate) async fn bind_listeners(
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Arc<RwLock<Option<Arc<MePool>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
tls_cache: Option<Arc<TlsFrontCache>>,
|
||||
ip_tracker: Arc<UserIpTracker>,
|
||||
@@ -237,7 +236,6 @@ pub(crate) async fn bind_listeners(
|
||||
let buffer_pool = buffer_pool.clone();
|
||||
let rng = rng.clone();
|
||||
let me_pool = me_pool.clone();
|
||||
let me_pool_runtime = me_pool_runtime.clone();
|
||||
let route_runtime = route_runtime.clone();
|
||||
let tls_cache = tls_cache.clone();
|
||||
let ip_tracker = ip_tracker.clone();
|
||||
@@ -300,7 +298,6 @@ pub(crate) async fn bind_listeners(
|
||||
let buffer_pool = buffer_pool.clone();
|
||||
let rng = rng.clone();
|
||||
let me_pool = me_pool.clone();
|
||||
let me_pool_runtime = me_pool_runtime.clone();
|
||||
let route_runtime = route_runtime.clone();
|
||||
let tls_cache = tls_cache.clone();
|
||||
let ip_tracker = ip_tracker.clone();
|
||||
@@ -310,8 +307,7 @@ pub(crate) async fn bind_listeners(
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
if let Err(e) =
|
||||
crate::proxy::client::handle_client_stream_with_shared_and_pool_runtime(
|
||||
if let Err(e) = crate::proxy::client::handle_client_stream_with_shared(
|
||||
stream,
|
||||
fake_peer,
|
||||
config,
|
||||
@@ -321,7 +317,6 @@ pub(crate) async fn bind_listeners(
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
Some(me_pool_runtime),
|
||||
route_runtime,
|
||||
tls_cache,
|
||||
ip_tracker,
|
||||
@@ -372,7 +367,6 @@ pub(crate) fn spawn_tcp_accept_loops(
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Arc<RwLock<Option<Arc<MePool>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
tls_cache: Option<Arc<TlsFrontCache>>,
|
||||
ip_tracker: Arc<UserIpTracker>,
|
||||
@@ -389,7 +383,6 @@ pub(crate) fn spawn_tcp_accept_loops(
|
||||
let buffer_pool = buffer_pool.clone();
|
||||
let rng = rng.clone();
|
||||
let me_pool = me_pool.clone();
|
||||
let me_pool_runtime = me_pool_runtime.clone();
|
||||
let route_runtime = route_runtime.clone();
|
||||
let tls_cache = tls_cache.clone();
|
||||
let ip_tracker = ip_tracker.clone();
|
||||
@@ -456,7 +449,6 @@ pub(crate) fn spawn_tcp_accept_loops(
|
||||
let buffer_pool = buffer_pool.clone();
|
||||
let rng = rng.clone();
|
||||
let me_pool = me_pool.clone();
|
||||
let me_pool_runtime = me_pool_runtime.clone();
|
||||
let route_runtime = route_runtime.clone();
|
||||
let tls_cache = tls_cache.clone();
|
||||
let ip_tracker = ip_tracker.clone();
|
||||
@@ -478,7 +470,6 @@ pub(crate) fn spawn_tcp_accept_loops(
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
Some(me_pool_runtime),
|
||||
route_runtime,
|
||||
tls_cache,
|
||||
ip_tracker,
|
||||
|
||||
@@ -36,10 +36,10 @@ use crate::network::probe::{decide_network_capabilities, log_probe_result, run_p
|
||||
use crate::proxy::route_mode::{RelayRouteMode, RouteRuntimeController};
|
||||
use crate::proxy::shared_state::ProxySharedState;
|
||||
use crate::startup::{
|
||||
COMPONENT_API_BOOTSTRAP, COMPONENT_CONFIG_LOAD, COMPONENT_DC_CONNECTIVITY_PING,
|
||||
COMPONENT_ME_CONNECTIVITY_PING, COMPONENT_ME_POOL_CONSTRUCT, COMPONENT_ME_POOL_INIT_STAGE1,
|
||||
COMPONENT_ME_PROXY_CONFIG_V4, COMPONENT_ME_PROXY_CONFIG_V6, COMPONENT_ME_SECRET_FETCH,
|
||||
COMPONENT_NETWORK_PROBE, COMPONENT_TRACING_INIT, StartupMeStatus, StartupTracker,
|
||||
COMPONENT_API_BOOTSTRAP, COMPONENT_CONFIG_LOAD, COMPONENT_ME_POOL_CONSTRUCT,
|
||||
COMPONENT_ME_POOL_INIT_STAGE1, COMPONENT_ME_PROXY_CONFIG_V4, COMPONENT_ME_PROXY_CONFIG_V6,
|
||||
COMPONENT_ME_SECRET_FETCH, COMPONENT_NETWORK_PROBE, COMPONENT_TRACING_INIT, StartupMeStatus,
|
||||
StartupTracker,
|
||||
};
|
||||
use crate::stats::beobachten::BeobachtenStore;
|
||||
use crate::stats::telemetry::TelemetryPolicy;
|
||||
@@ -461,14 +461,12 @@ async fn run_telemt_core(
|
||||
|
||||
let (api_config_tx, api_config_rx) = watch::channel(Arc::new(config.clone()));
|
||||
let (detected_ips_tx, detected_ips_rx) = watch::channel((None::<IpAddr>, None::<IpAddr>));
|
||||
let initial_direct_first =
|
||||
config.general.use_middle_proxy && config.general.me2dc_fallback;
|
||||
let initial_admission_open = !config.general.use_middle_proxy || initial_direct_first;
|
||||
let initial_admission_open = !config.general.use_middle_proxy;
|
||||
let (admission_tx, admission_rx) = watch::channel(initial_admission_open);
|
||||
let initial_route_mode = if !config.general.use_middle_proxy || initial_direct_first {
|
||||
RelayRouteMode::Direct
|
||||
} else {
|
||||
let initial_route_mode = if config.general.use_middle_proxy {
|
||||
RelayRouteMode::Middle
|
||||
} else {
|
||||
RelayRouteMode::Direct
|
||||
};
|
||||
let route_runtime = Arc::new(RouteRuntimeController::new(initial_route_mode));
|
||||
let api_me_pool = Arc::new(RwLock::new(None::<Arc<MePool>>));
|
||||
@@ -604,9 +602,8 @@ async fn run_telemt_core(
|
||||
let me_init_retry_attempts = config.general.me_init_retry_attempts;
|
||||
if use_middle_proxy && !decision.ipv4_me && !decision.ipv6_me {
|
||||
if me2dc_fallback {
|
||||
warn!(
|
||||
"No usable IP family for Middle Proxy detected; Direct-DC startup fallback is active while ME init retries continue"
|
||||
);
|
||||
warn!("No usable IP family for Middle Proxy detected; falling back to direct DC");
|
||||
use_middle_proxy = false;
|
||||
} else {
|
||||
warn!(
|
||||
"No usable IP family for Middle Proxy detected; me2dc_fallback=false, ME init retries stay active"
|
||||
@@ -668,32 +665,23 @@ async fn run_telemt_core(
|
||||
}
|
||||
|
||||
let (me_ready_tx, me_ready_rx) = watch::channel(0_u64);
|
||||
let direct_first_startup = use_middle_proxy && me2dc_fallback;
|
||||
|
||||
let me_pool: Option<Arc<MePool>> = if direct_first_startup {
|
||||
None
|
||||
} else {
|
||||
me_startup::initialize_me_pool(
|
||||
use_middle_proxy,
|
||||
&config,
|
||||
&decision,
|
||||
&probe,
|
||||
&startup_tracker,
|
||||
upstream_manager.clone(),
|
||||
rng.clone(),
|
||||
stats.clone(),
|
||||
api_me_pool.clone(),
|
||||
me_ready_tx.clone(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
let me_pool: Option<Arc<MePool>> = me_startup::initialize_me_pool(
|
||||
use_middle_proxy,
|
||||
&config,
|
||||
&decision,
|
||||
&probe,
|
||||
&startup_tracker,
|
||||
upstream_manager.clone(),
|
||||
rng.clone(),
|
||||
stats.clone(),
|
||||
api_me_pool.clone(),
|
||||
me_ready_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// If ME failed to initialize, force direct-only mode.
|
||||
if direct_first_startup {
|
||||
startup_tracker.set_transport_mode("direct").await;
|
||||
startup_tracker.set_degraded(true).await;
|
||||
info!("Transport: Direct DC startup fallback active; Middle-End bootstrap continues in background");
|
||||
} else if me_pool.is_some() {
|
||||
if me_pool.is_some() {
|
||||
startup_tracker.set_transport_mode("middle_proxy").await;
|
||||
startup_tracker.set_degraded(false).await;
|
||||
info!("Transport: Middle-End Proxy - all DC-over-RPC");
|
||||
@@ -731,33 +719,18 @@ async fn run_telemt_core(
|
||||
config.access.cidr_rate_limits.clone(),
|
||||
);
|
||||
|
||||
if direct_first_startup {
|
||||
startup_tracker
|
||||
.skip_component(
|
||||
COMPONENT_ME_CONNECTIVITY_PING,
|
||||
Some("deferred by direct-first startup".to_string()),
|
||||
)
|
||||
.await;
|
||||
startup_tracker
|
||||
.skip_component(
|
||||
COMPONENT_DC_CONNECTIVITY_PING,
|
||||
Some("background health checks active".to_string()),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
connectivity::run_startup_connectivity(
|
||||
&config,
|
||||
&me_pool,
|
||||
rng.clone(),
|
||||
&startup_tracker,
|
||||
upstream_manager.clone(),
|
||||
prefer_ipv6,
|
||||
&decision,
|
||||
process_started_at,
|
||||
api_me_pool.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
connectivity::run_startup_connectivity(
|
||||
&config,
|
||||
&me_pool,
|
||||
rng.clone(),
|
||||
&startup_tracker,
|
||||
upstream_manager.clone(),
|
||||
prefer_ipv6,
|
||||
&decision,
|
||||
process_started_at,
|
||||
api_me_pool.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let runtime_watches = runtime_tasks::spawn_runtime_tasks(
|
||||
&config,
|
||||
@@ -785,70 +758,9 @@ async fn run_telemt_core(
|
||||
let detected_ip_v4 = runtime_watches.detected_ip_v4;
|
||||
let detected_ip_v6 = runtime_watches.detected_ip_v6;
|
||||
|
||||
if direct_first_startup {
|
||||
let config_bg = config.clone();
|
||||
let decision_bg = decision.clone();
|
||||
let probe_bg = probe.clone();
|
||||
let startup_tracker_bg = startup_tracker.clone();
|
||||
let upstream_manager_bg = upstream_manager.clone();
|
||||
let rng_bg = rng.clone();
|
||||
let stats_bg = stats.clone();
|
||||
let api_me_pool_bg = api_me_pool.clone();
|
||||
let me_ready_tx_bg = me_ready_tx.clone();
|
||||
let config_rx_bg = config_rx.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut bootstrap_attempt: u32 = 0;
|
||||
loop {
|
||||
bootstrap_attempt = bootstrap_attempt.saturating_add(1);
|
||||
let pool = me_startup::initialize_me_pool(
|
||||
true,
|
||||
config_bg.as_ref(),
|
||||
&decision_bg,
|
||||
&probe_bg,
|
||||
&startup_tracker_bg,
|
||||
upstream_manager_bg.clone(),
|
||||
rng_bg.clone(),
|
||||
stats_bg.clone(),
|
||||
api_me_pool_bg.clone(),
|
||||
me_ready_tx_bg.clone(),
|
||||
)
|
||||
.await;
|
||||
if let Some(pool) = pool {
|
||||
runtime_tasks::spawn_middle_proxy_runtime_tasks(
|
||||
config_bg.as_ref(),
|
||||
config_rx_bg,
|
||||
pool,
|
||||
rng_bg,
|
||||
me_ready_tx_bg,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if me_init_retry_attempts > 0 && bootstrap_attempt >= me_init_retry_attempts {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
});
|
||||
|
||||
let startup_tracker_ready = startup_tracker.clone();
|
||||
let api_me_pool_ready = api_me_pool.clone();
|
||||
let mut me_ready_rx_transport = me_ready_tx.subscribe();
|
||||
tokio::spawn(async move {
|
||||
if me_ready_rx_transport.changed().await.is_ok() {
|
||||
if let Some(pool) = api_me_pool_ready.read().await.as_ref() {
|
||||
pool.set_runtime_ready(true);
|
||||
}
|
||||
startup_tracker_ready.set_transport_mode("middle_proxy").await;
|
||||
startup_tracker_ready.set_degraded(false).await;
|
||||
info!("Transport: Middle-End Proxy restored for new sessions");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
admission::configure_admission_gate(
|
||||
&config,
|
||||
me_pool.clone(),
|
||||
api_me_pool.clone(),
|
||||
route_runtime.clone(),
|
||||
&admission_tx,
|
||||
config_rx.clone(),
|
||||
@@ -877,7 +789,6 @@ async fn run_telemt_core(
|
||||
buffer_pool.clone(),
|
||||
rng.clone(),
|
||||
me_pool.clone(),
|
||||
api_me_pool.clone(),
|
||||
route_runtime.clone(),
|
||||
tls_cache.clone(),
|
||||
ip_tracker.clone(),
|
||||
@@ -932,7 +843,6 @@ async fn run_telemt_core(
|
||||
buffer_pool.clone(),
|
||||
rng.clone(),
|
||||
me_pool.clone(),
|
||||
api_me_pool.clone(),
|
||||
route_runtime.clone(),
|
||||
tls_cache.clone(),
|
||||
ip_tracker.clone(),
|
||||
|
||||
@@ -257,7 +257,45 @@ pub(crate) async fn spawn_runtime_tasks(
|
||||
});
|
||||
|
||||
if let Some(pool) = me_pool {
|
||||
spawn_middle_proxy_runtime_tasks(config, config_rx.clone(), pool, rng, me_ready_tx);
|
||||
let reinit_trigger_capacity = config.general.me_reinit_trigger_channel.max(1);
|
||||
let (reinit_tx, reinit_rx) = mpsc::channel::<MeReinitTrigger>(reinit_trigger_capacity);
|
||||
|
||||
let pool_clone_sched = pool.clone();
|
||||
let rng_clone_sched = rng.clone();
|
||||
let config_rx_clone_sched = config_rx.clone();
|
||||
let me_ready_tx_sched = me_ready_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
crate::transport::middle_proxy::me_reinit_scheduler(
|
||||
pool_clone_sched,
|
||||
rng_clone_sched,
|
||||
config_rx_clone_sched,
|
||||
reinit_rx,
|
||||
me_ready_tx_sched,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let pool_clone = pool.clone();
|
||||
let config_rx_clone = config_rx.clone();
|
||||
let reinit_tx_updater = reinit_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
crate::transport::middle_proxy::me_config_updater(
|
||||
pool_clone,
|
||||
config_rx_clone,
|
||||
reinit_tx_updater,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let config_rx_clone_rot = config_rx.clone();
|
||||
let reinit_tx_rotation = reinit_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
crate::transport::middle_proxy::me_rotation_task(
|
||||
config_rx_clone_rot,
|
||||
reinit_tx_rotation,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
RuntimeWatches {
|
||||
@@ -268,51 +306,6 @@ pub(crate) async fn spawn_runtime_tasks(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_middle_proxy_runtime_tasks(
|
||||
config: &ProxyConfig,
|
||||
config_rx: watch::Receiver<Arc<ProxyConfig>>,
|
||||
pool: Arc<MePool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_ready_tx: watch::Sender<u64>,
|
||||
) {
|
||||
let reinit_trigger_capacity = config.general.me_reinit_trigger_channel.max(1);
|
||||
let (reinit_tx, reinit_rx) = mpsc::channel::<MeReinitTrigger>(reinit_trigger_capacity);
|
||||
|
||||
let pool_clone_sched = pool.clone();
|
||||
let rng_clone_sched = rng.clone();
|
||||
let config_rx_clone_sched = config_rx.clone();
|
||||
let me_ready_tx_sched = me_ready_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
crate::transport::middle_proxy::me_reinit_scheduler(
|
||||
pool_clone_sched,
|
||||
rng_clone_sched,
|
||||
config_rx_clone_sched,
|
||||
reinit_rx,
|
||||
me_ready_tx_sched,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let pool_clone = pool.clone();
|
||||
let config_rx_clone = config_rx.clone();
|
||||
let reinit_tx_updater = reinit_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
crate::transport::middle_proxy::me_config_updater(
|
||||
pool_clone,
|
||||
config_rx_clone,
|
||||
reinit_tx_updater,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let config_rx_clone_rot = config_rx.clone();
|
||||
let reinit_tx_rotation = reinit_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
crate::transport::middle_proxy::me_rotation_task(config_rx_clone_rot, reinit_tx_rotation)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_runtime_log_filter(
|
||||
has_rust_log: bool,
|
||||
effective_log_level: &LogLevel,
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time::timeout;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
@@ -453,50 +452,7 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(dead_code)]
|
||||
pub async fn handle_client_stream_with_shared<S>(
|
||||
stream: S,
|
||||
peer: SocketAddr,
|
||||
config: Arc<ProxyConfig>,
|
||||
stats: Arc<Stats>,
|
||||
upstream_manager: Arc<UpstreamManager>,
|
||||
replay_checker: Arc<ReplayChecker>,
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
tls_cache: Option<Arc<TlsFrontCache>>,
|
||||
ip_tracker: Arc<UserIpTracker>,
|
||||
beobachten: Arc<BeobachtenStore>,
|
||||
shared: Arc<ProxySharedState>,
|
||||
proxy_protocol_enabled: bool,
|
||||
) -> Result<()>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
handle_client_stream_with_shared_and_pool_runtime(
|
||||
stream,
|
||||
peer,
|
||||
config,
|
||||
stats,
|
||||
upstream_manager,
|
||||
replay_checker,
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
None,
|
||||
route_runtime,
|
||||
tls_cache,
|
||||
ip_tracker,
|
||||
beobachten,
|
||||
shared,
|
||||
proxy_protocol_enabled,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn handle_client_stream_with_shared_and_pool_runtime<S>(
|
||||
mut stream: S,
|
||||
peer: SocketAddr,
|
||||
config: Arc<ProxyConfig>,
|
||||
@@ -506,7 +462,6 @@ pub async fn handle_client_stream_with_shared_and_pool_runtime<S>(
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Option<Arc<RwLock<Option<Arc<MePool>>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
tls_cache: Option<Arc<TlsFrontCache>>,
|
||||
ip_tracker: Arc<UserIpTracker>,
|
||||
@@ -776,7 +731,6 @@ where
|
||||
RunningClientHandler::handle_authenticated_static_with_shared(
|
||||
crypto_reader, crypto_writer, success,
|
||||
upstream_manager, stats, config, buffer_pool, rng, me_pool,
|
||||
me_pool_runtime,
|
||||
route_runtime.clone(),
|
||||
local_addr, real_peer, ip_tracker.clone(),
|
||||
shared.clone(),
|
||||
@@ -837,7 +791,6 @@ where
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
me_pool_runtime,
|
||||
route_runtime.clone(),
|
||||
local_addr,
|
||||
real_peer,
|
||||
@@ -893,7 +846,6 @@ pub struct RunningClientHandler {
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Option<Arc<RwLock<Option<Arc<MePool>>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
tls_cache: Option<Arc<TlsFrontCache>>,
|
||||
ip_tracker: Arc<UserIpTracker>,
|
||||
@@ -939,7 +891,6 @@ impl ClientHandler {
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
None,
|
||||
route_runtime,
|
||||
tls_cache,
|
||||
ip_tracker,
|
||||
@@ -964,7 +915,6 @@ impl ClientHandler {
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Option<Arc<RwLock<Option<Arc<MePool>>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
tls_cache: Option<Arc<TlsFrontCache>>,
|
||||
ip_tracker: Arc<UserIpTracker>,
|
||||
@@ -988,7 +938,6 @@ impl ClientHandler {
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
me_pool_runtime,
|
||||
route_runtime,
|
||||
tls_cache,
|
||||
ip_tracker,
|
||||
@@ -1396,7 +1345,6 @@ impl RunningClientHandler {
|
||||
buffer_pool,
|
||||
self.rng,
|
||||
self.me_pool,
|
||||
self.me_pool_runtime,
|
||||
self.route_runtime.clone(),
|
||||
local_addr,
|
||||
peer,
|
||||
@@ -1481,7 +1429,6 @@ impl RunningClientHandler {
|
||||
buffer_pool,
|
||||
self.rng,
|
||||
self.me_pool,
|
||||
self.me_pool_runtime,
|
||||
self.route_runtime.clone(),
|
||||
local_addr,
|
||||
peer,
|
||||
@@ -1525,7 +1472,6 @@ impl RunningClientHandler {
|
||||
buffer_pool,
|
||||
rng,
|
||||
me_pool,
|
||||
None,
|
||||
route_runtime,
|
||||
local_addr,
|
||||
peer_addr,
|
||||
@@ -1545,7 +1491,6 @@ impl RunningClientHandler {
|
||||
buffer_pool: Arc<BufferPool>,
|
||||
rng: Arc<SecureRandom>,
|
||||
me_pool: Option<Arc<MePool>>,
|
||||
me_pool_runtime: Option<Arc<RwLock<Option<Arc<MePool>>>>>,
|
||||
route_runtime: Arc<RouteRuntimeController>,
|
||||
local_addr: SocketAddr,
|
||||
peer_addr: SocketAddr,
|
||||
@@ -1576,29 +1521,15 @@ impl RunningClientHandler {
|
||||
|
||||
let route_snapshot = route_runtime.snapshot();
|
||||
let session_id = rng.u64();
|
||||
let selected_me_pool = if config.general.use_middle_proxy
|
||||
&& matches!(route_snapshot.mode, RelayRouteMode::Middle)
|
||||
{
|
||||
if let Some(ref pool) = me_pool {
|
||||
Some(pool.clone())
|
||||
} else if let Some(pool_runtime) = me_pool_runtime.as_ref() {
|
||||
pool_runtime.read().await.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let relay_result = if config.general.use_middle_proxy
|
||||
&& matches!(route_snapshot.mode, RelayRouteMode::Middle)
|
||||
{
|
||||
if let Some(pool) = selected_me_pool {
|
||||
if let Some(ref pool) = me_pool {
|
||||
handle_via_middle_proxy(
|
||||
client_reader,
|
||||
client_writer,
|
||||
success,
|
||||
pool,
|
||||
pool.clone(),
|
||||
stats.clone(),
|
||||
config,
|
||||
buffer_pool,
|
||||
|
||||
@@ -47,12 +47,6 @@ struct CopyOutcome {
|
||||
ended_by_eof: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct MaskTcpTarget<'a> {
|
||||
host: &'a str,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
async fn copy_with_idle_timeout<R, W>(
|
||||
reader: &mut R,
|
||||
writer: &mut W,
|
||||
@@ -337,9 +331,7 @@ async fn wait_mask_outcome_budget(started: Instant, config: &ProxyConfig) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tls_domain_mask_host_tests {
|
||||
use super::{
|
||||
mask_host_for_initial_data, mask_tcp_target_for_initial_data, matching_tls_domain_for_sni,
|
||||
};
|
||||
use super::{mask_host_for_initial_data, matching_tls_domain_for_sni};
|
||||
use crate::config::ProxyConfig;
|
||||
|
||||
fn client_hello_with_sni(sni_host: &str) -> Vec<u8> {
|
||||
@@ -418,25 +410,6 @@ mod tls_domain_mask_host_tests {
|
||||
|
||||
assert_eq!(mask_host_for_initial_data(&config, &initial_data), "b.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusive_mask_target_overrides_only_matching_sni() {
|
||||
let mut config = config_with_tls_domains();
|
||||
config
|
||||
.censorship
|
||||
.exclusive_mask
|
||||
.insert("b.com".to_string(), "origin-b.example:8443".to_string());
|
||||
let b_initial_data = client_hello_with_sni("B.COM");
|
||||
let c_initial_data = client_hello_with_sni("c.com");
|
||||
|
||||
let b_target = mask_tcp_target_for_initial_data(&config, &b_initial_data);
|
||||
let c_target = mask_tcp_target_for_initial_data(&config, &c_initial_data);
|
||||
|
||||
assert_eq!(b_target.host, "origin-b.example");
|
||||
assert_eq!(b_target.port, 8443);
|
||||
assert_eq!(c_target.host, "c.com");
|
||||
assert_eq!(c_target.port, config.censorship.mask_port);
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect client type based on initial data
|
||||
@@ -485,61 +458,7 @@ fn matching_tls_domain_for_sni<'a>(config: &'a ProxyConfig, sni: &str) -> Option
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_exclusive_mask_target(target: &str) -> Option<MaskTcpTarget<'_>> {
|
||||
let target = target.trim();
|
||||
if target.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if target.starts_with('[') {
|
||||
let end = target.find(']')?;
|
||||
if target.get(end + 1..end + 2)? != ":" {
|
||||
return None;
|
||||
}
|
||||
let port = target[end + 2..].parse::<u16>().ok()?;
|
||||
return (port > 0).then_some(MaskTcpTarget {
|
||||
host: &target[..=end],
|
||||
port,
|
||||
});
|
||||
}
|
||||
|
||||
let (host, port) = target.rsplit_once(':')?;
|
||||
if host.is_empty() || host.contains(':') {
|
||||
return None;
|
||||
}
|
||||
let port = port.parse::<u16>().ok()?;
|
||||
(port > 0).then_some(MaskTcpTarget { host, port })
|
||||
}
|
||||
|
||||
fn exclusive_mask_target_for_sni<'a>(
|
||||
config: &'a ProxyConfig,
|
||||
sni: &str,
|
||||
) -> Option<MaskTcpTarget<'a>> {
|
||||
for (domain, target) in &config.censorship.exclusive_mask {
|
||||
if domain.eq_ignore_ascii_case(sni) {
|
||||
return parse_exclusive_mask_target(target);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn mask_host_for_initial_data<'a>(config: &'a ProxyConfig, initial_data: &[u8]) -> &'a str {
|
||||
mask_tcp_target_for_initial_data(config, initial_data).host
|
||||
}
|
||||
|
||||
fn mask_tcp_target_for_initial_data<'a>(
|
||||
config: &'a ProxyConfig,
|
||||
initial_data: &[u8],
|
||||
) -> MaskTcpTarget<'a> {
|
||||
if let Some(target) = tls::extract_sni_from_client_hello(initial_data)
|
||||
.as_deref()
|
||||
.and_then(|sni| exclusive_mask_target_for_sni(config, sni))
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
let configured_mask_host = config
|
||||
.censorship
|
||||
.mask_host
|
||||
@@ -547,20 +466,13 @@ fn mask_tcp_target_for_initial_data<'a>(
|
||||
.unwrap_or(&config.censorship.tls_domain);
|
||||
|
||||
if !configured_mask_host.eq_ignore_ascii_case(&config.censorship.tls_domain) {
|
||||
return MaskTcpTarget {
|
||||
host: configured_mask_host,
|
||||
port: config.censorship.mask_port,
|
||||
};
|
||||
return configured_mask_host;
|
||||
}
|
||||
|
||||
let host = tls::extract_sni_from_client_hello(initial_data)
|
||||
tls::extract_sni_from_client_hello(initial_data)
|
||||
.as_deref()
|
||||
.and_then(|sni| matching_tls_domain_for_sni(config, sni))
|
||||
.unwrap_or(configured_mask_host);
|
||||
MaskTcpTarget {
|
||||
host,
|
||||
port: config.censorship.mask_port,
|
||||
}
|
||||
.unwrap_or(configured_mask_host)
|
||||
}
|
||||
|
||||
fn canonical_ip(ip: IpAddr) -> IpAddr {
|
||||
@@ -858,15 +770,9 @@ pub async fn handle_bad_client<R, W>(
|
||||
return;
|
||||
}
|
||||
|
||||
let exclusive_tcp_target = tls::extract_sni_from_client_hello(initial_data)
|
||||
.as_deref()
|
||||
.and_then(|sni| exclusive_mask_target_for_sni(config, sni));
|
||||
|
||||
// Connect via Unix socket or TCP
|
||||
#[cfg(unix)]
|
||||
if exclusive_tcp_target.is_none()
|
||||
&& let Some(ref sock_path) = config.censorship.mask_unix_sock
|
||||
{
|
||||
if let Some(ref sock_path) = config.censorship.mask_unix_sock {
|
||||
let outcome_started = Instant::now();
|
||||
let connect_started = Instant::now();
|
||||
debug!(
|
||||
@@ -943,10 +849,8 @@ pub async fn handle_bad_client<R, W>(
|
||||
return;
|
||||
}
|
||||
|
||||
let mask_target = exclusive_tcp_target
|
||||
.unwrap_or_else(|| mask_tcp_target_for_initial_data(config, initial_data));
|
||||
let mask_host = mask_target.host;
|
||||
let mask_port = mask_target.port;
|
||||
let mask_host = mask_host_for_initial_data(config, initial_data);
|
||||
let mask_port = config.censorship.mask_port;
|
||||
|
||||
// Fail closed when fallback points at our own listener endpoint.
|
||||
// Self-referential masking can create recursive proxy loops under
|
||||
|
||||
@@ -19,14 +19,12 @@ impl MePool {
|
||||
.me_reconnect_max_concurrent_per_dc
|
||||
.max(1) as usize;
|
||||
let ks = self.key_selector().await;
|
||||
let me_servers = self.proxy_map_v4.read().await.len();
|
||||
let secret_len = self.proxy_secret.read().await.secret.len();
|
||||
info!(
|
||||
me_servers,
|
||||
me_servers = self.proxy_map_v4.read().await.len(),
|
||||
pool_size,
|
||||
connect_concurrency,
|
||||
key_selector = format_args!("0x{ks:08x}"),
|
||||
secret_len,
|
||||
secret_len = self.proxy_secret.read().await.secret.len(),
|
||||
"Initializing ME pool"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user