Compare commits

..

10 Commits

Author SHA1 Message Date
Alexey
2df6b8704d BSD Support + Active IP in API + Timeouts tuning + Apple/XNU Connectivity fixes + Admission-timeouts + Global Each TCP Connections: merge pull request #611 from telemt/flow
BSD Support + Active IP in API + Timeouts tuning + Apple/XNU Connectivity fixes + Admission-timeouts + Global Each TCP Connections
2026-03-31 13:10:31 +03:00
Alexey
5f5a046710 Update Cargo.toml + Cargo.lock 2026-03-31 13:04:24 +03:00
Alexey
2dc81ad0e0 API Consistency fixes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
2026-03-31 13:03:05 +03:00
Alexey
d8d8534cf8 Update masking_ab_envelope_blur_integration_security_tests.rs 2026-03-31 12:30:43 +03:00
Alexey
f3e9d00132 Merge pull request #605 from telemt/readme
Readme
2026-03-29 11:52:44 +03:00
Alexey
dee6e13fef Update CONTRIBUTING.md 2026-03-29 01:51:51 +03:00
Alexey
cba837745b Merge pull request #599 from Dimasssss/main
Update FAQ
2026-03-28 12:28:04 +03:00
Dimasssss
876c8f1612 Update FAQ.en.md 2026-03-27 22:26:21 +03:00
Dimasssss
ac8ad864be Update FAQ.ru.md 2026-03-27 22:26:07 +03:00
Alexey
fe56dc7c1a Update README.md 2026-03-27 14:13:08 +03:00
10 changed files with 294 additions and 171 deletions

View File

@@ -1,19 +1,82 @@
# Issues - Rules # Issues
## Warnung
Before opening Issue, if it is more question than problem or bug - ask about that [in our chat](https://t.me/telemtrs)
## What it is not ## What it is not
- NOT Question and Answer - NOT Question and Answer
- NOT Helpdesk - NOT Helpdesk
# Pull Requests - Rules ***Each of your Issues triggers attempts to reproduce problems and analyze them, which are done manually by people***
---
# Pull Requests
## General ## General
- ONLY signed and verified commits - ONLY signed and verified commits
- ONLY from your name - ONLY from your name
- DO NOT commit with `codex` or `claude` as author/commiter - DO NOT commit with `codex`, `claude`, or other AI tools as author/committer
- PREFER `flow` branch for development, not `main` - PREFER `flow` branch for development, not `main`
## AI ---
We are not against modern tools, like AI, where you act as a principal or architect, but we consider it important:
- you really understand what you're doing ## Definition of Ready (MANDATORY)
- you understand the relationships and dependencies of the components being modified
- you understand the architecture of Telegram MTProto, MTProxy, Middle-End KDF at least generically A Pull Request WILL be ignored or closed if:
- you DO NOT commit for the sake of commits, but to help the community, core-developers and ordinary users
- it does NOT build
- it does NOT pass tests
- it does NOT follow formatting rules
- it contains unrelated or excessive changes
- the author cannot clearly explain the change
---
## Blessed Principles
- PR must build
- PR must pass tests
- PR must be understood by author
---
## AI Usage Policy
AI tools (Claude, ChatGPT, Codex, DeepSeek, etc.) are allowed as **assistants**, NOT as decision-makers.
By submitting a PR, you confirm that:
- you fully understand the code you submit
- you verified correctness manually
- you reviewed architecture and dependencies
- you take full responsibility for the change
AI-generated code is treated as **draft** and must be validated like any other external contribution.
PRs that look like unverified AI dumps WILL be closed
---
## Maintainer Policy
Maintainers reserve the right to:
- close PRs that do not meet basic quality requirements
- request explanations before review
- ignore low-effort contributions
Respect the reviewers time
---
## Enforcement
Pull Requests that violate project standards may be closed without review.
This includes (but is not limited to):
- non-building code
- failing tests
- 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

2
Cargo.lock generated
View File

@@ -2793,7 +2793,7 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]] [[package]]
name = "telemt" name = "telemt"
version = "3.3.32" version = "3.3.35"
dependencies = [ dependencies = [
"aes", "aes",
"anyhow", "anyhow",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "telemt" name = "telemt"
version = "3.3.34" version = "3.3.35"
edition = "2024" edition = "2024"
[features] [features]

View File

@@ -2,6 +2,8 @@
***Löst Probleme, bevor andere überhaupt wissen, dass sie existieren*** / ***It solves problems before others even realize they exist*** ***Löst Probleme, bevor andere überhaupt wissen, dass sie existieren*** / ***It solves problems before others even realize they exist***
[**Telemt Chat in Telegram**](https://t.me/telemtrs)
**Telemt** is a fast, secure, and feature-rich server written in Rust: it fully implements the official Telegram proxy algo and adds many production-ready improvements such as: **Telemt** is a fast, secure, and feature-rich server written in Rust: it fully implements the official Telegram proxy algo and adds many production-ready improvements such as:
- [ME Pool + Reader/Writer + Registry + Refill + Adaptive Floor + Trio-State + Generation Lifecycle](https://github.com/telemt/telemt/blob/main/docs/model/MODEL.en.md) - [ME Pool + Reader/Writer + Registry + Refill + Adaptive Floor + Trio-State + Generation Lifecycle](https://github.com/telemt/telemt/blob/main/docs/model/MODEL.en.md)
- [Full-covered API w/ management](https://github.com/telemt/telemt/blob/main/docs/API.md) - [Full-covered API w/ management](https://github.com/telemt/telemt/blob/main/docs/API.md)
@@ -9,60 +11,6 @@
- Prometheus-format Metrics - Prometheus-format Metrics
- TLS-Fronting and TCP-Splicing for masking from "prying" eyes - TLS-Fronting and TCP-Splicing for masking from "prying" eyes
[**Telemt Chat in Telegram**](https://t.me/telemtrs)
## NEWS and EMERGENCY
### ✈️ Telemt 3 is released!
<table>
<tr>
<td width="50%" valign="top">
### 🇷🇺 RU
#### О релизах
[3.3.27](https://github.com/telemt/telemt/releases/tag/3.3.27) даёт баланс стабильности и передового функционала, а так же последние исправления по безопасности и багам
Будем рады вашему фидбеку и предложениям по улучшению — особенно в части **API**, **статистики**, **UX**
---
Если у вас есть компетенции в:
- Асинхронных сетевых приложениях
- Анализе трафика
- Реверс-инжиниринге
- Сетевых расследованиях
Мы открыты к архитектурным предложениям, идеям и pull requests
</td>
<td width="50%" valign="top">
### 🇬🇧 EN
#### About releases
[3.3.27](https://github.com/telemt/telemt/releases/tag/3.3.27) provides a balance of stability and advanced functionality, as well as the latest security and bug fixes
We are looking forward to your feedback and improvement proposals — especially regarding **API**, **statistics**, **UX**
---
If you have expertise in:
- Asynchronous network applications
- Traffic analysis
- Reverse engineering
- Network forensics
We welcome ideas, architectural feedback, and pull requests.
</td>
</tr>
</table>
# Features
💥 The configuration structure has changed since version 1.1.0.0. change it in your environment!
⚓ Our implementation of **TLS-fronting** is one of the most deeply debugged, focused, advanced and *almost* **"behaviorally consistent to real"**: we are confident we have it right - [see evidence on our validation and traces](#recognizability-for-dpi-and-crawler) ⚓ Our implementation of **TLS-fronting** is one of the most deeply debugged, focused, advanced and *almost* **"behaviorally consistent to real"**: we are confident we have it right - [see evidence on our validation and traces](#recognizability-for-dpi-and-crawler)
⚓ Our ***Middle-End Pool*** is fastest by design in standard scenarios, compared to other implementations of connecting to the Middle-End Proxy: non dramatically, but usual ⚓ Our ***Middle-End Pool*** is fastest by design in standard scenarios, compared to other implementations of connecting to the Middle-End Proxy: non dramatically, but usual

View File

@@ -1,113 +1,122 @@
## How to set up "proxy sponsor" channel and statistics via @MTProxybot bot ## How to set up a "proxy sponsor" channel and statistics via the @MTProxybot
1. Go to @MTProxybot bot. 1. Go to the @MTProxybot.
2. Enter the command `/newproxy` 2. Enter the `/newproxy` command.
3. Send the server IP and port. For example: 1.2.3.4:443 3. Send your server's IP address and port. For example: `1.2.3.4:443`.
4. Open the config `nano /etc/telemt/telemt.toml`. 4. Open the configuration file: `nano /etc/telemt/telemt.toml`.
5. Copy and send the user secret from the [access.users] section to the bot. 5. Copy and send the user secret from the `[access.users]` section to the bot.
6. Copy the tag received from the bot. For example 1234567890abcdef1234567890abcdef. 6. Copy the tag provided by the bot. For example: `1234567890abcdef1234567890abcdef`.
> [!WARNING] > [!WARNING]
> The link provided by the bot will not work. Do not copy or use it! > The link provided by the bot will not work. Do not copy or use it!
7. Uncomment the ad_tag parameter and enter the tag received from the bot. 7. Uncomment the `ad_tag` parameter and enter the tag received from the bot.
8. Uncomment/add the parameter `use_middle_proxy = true`. 8. Uncomment or add the `use_middle_proxy = true` parameter.
Config example: Configuration example:
```toml ```toml
[general] [general]
ad_tag = "1234567890abcdef1234567890abcdef" ad_tag = "1234567890abcdef1234567890abcdef"
use_middle_proxy = true use_middle_proxy = true
``` ```
9. Save the config. Ctrl+S -> Ctrl+X. 9. Save the changes (in nano: Ctrl+S -> Ctrl+X).
10. Restart telemt `systemctl restart telemt`. 10. Restart the telemt service: `systemctl restart telemt`.
11. In the bot, send the command /myproxies and select the added server. 11. Send the `/myproxies` command to the bot and select the added server.
12. Click the "Set promotion" button. 12. Click the "Set promotion" button.
13. Send a **public link** to the channel. Private channels cannot be added! 13. Send a **public link** to the channel. Private channels cannot be added!
14. Wait approximately 1 hour for the information to update on Telegram servers. 14. Wait for about 1 hour for the information to update on Telegram servers.
> [!WARNING] > [!WARNING]
> You will not see the "proxy sponsor" if you are already subscribed to the channel. > The sponsored channel will not be displayed to you if you are already subscribed to it.
**You can also set up different channels for different users.** **You can also configure different sponsored channels for different users:**
```toml ```toml
[access.user_ad_tags] [access.user_ad_tags]
hello = "ad_tag" hello = "ad_tag"
hello2 = "ad_tag2" hello2 = "ad_tag2"
``` ```
## Why is middle proxy (ME) needed ## Why do you need a middle proxy (ME)
https://github.com/telemt/telemt/discussions/167 https://github.com/telemt/telemt/discussions/167
## How many people can use 1 link
By default, 1 link can be used by any number of people. ## How many people can use one link
You can limit the number of IPs using the proxy.
By default, an unlimited number of people can use a single link.
However, you can limit the number of unique IP addresses for each user:
```toml ```toml
[access.user_max_unique_ips] [access.user_max_unique_ips]
hello = 1 hello = 1
``` ```
This parameter limits how many unique IPs can use 1 link simultaneously. If one user disconnects, a second user can connect. Also, multiple users can sit behind the same IP. This parameter sets the maximum number of unique IP addresses from which a single link can be used simultaneously. If the first user disconnects, a second one can connect. At the same time, multiple users can connect from a single IP address simultaneously (for example, devices on the same Wi-Fi network).
## How to create multiple different links ## How to create multiple different links
1. Generate the required number of secrets `openssl rand -hex 16` 1. Generate the required number of secrets using the command: `openssl rand -hex 16`.
2. Open the config `nano /etc/telemt.toml` 2. Open the configuration file: `nano /etc/telemt/telemt.toml`.
3. Add new users. 3. Add new users to the `[access.users]` section:
```toml ```toml
[access.users] [access.users]
user1 = "00000000000000000000000000000001" user1 = "00000000000000000000000000000001"
user2 = "00000000000000000000000000000002" user2 = "00000000000000000000000000000002"
user3 = "00000000000000000000000000000003" user3 = "00000000000000000000000000000003"
``` ```
4. Save the config. Ctrl+S -> Ctrl+X. You don't need to restart telemt. 4. Save the configuration (Ctrl+S -> Ctrl+X). There is no need to restart the telemt service.
5. Get the links via 5. Get the ready-to-use links using the command:
```bash ```bash
curl -s http://127.0.0.1:9091/v1/users | jq curl -s http://127.0.0.1:9091/v1/users | jq
``` ```
## "Unknown TLS SNI" Error ## "Unknown TLS SNI" error
You probably updated tls_domain, but users are still connecting via old links with the previous domain. Usually, this error occurs if you have changed the `tls_domain` parameter, but users continue to connect using old links with the previous domain.
If you need to allow connections with any domains (ignoring SNI mismatches), add the following parameters:
```toml
[censorship]
unknown_sni_action = "mask"
```
## How to view metrics ## How to view metrics
1. Open the config `nano /etc/telemt/telemt.toml` 1. Open the configuration file: `nano /etc/telemt/telemt.toml`.
2. Add the following parameters 2. Add the following parameters:
```toml ```toml
[server] [server]
metrics_port = 9090 metrics_port = 9090
metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"] metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"]
``` ```
3. Save the config. Ctrl+S -> Ctrl+X. 3. Save the changes (Ctrl+S -> Ctrl+X).
4. Metrics are available at SERVER_IP:9090/metrics. 4. After that, metrics will be available at: `SERVER_IP:9090/metrics`.
> [!WARNING] > [!WARNING]
> "0.0.0.0/0" in metrics_whitelist opens access from any IP. Replace with your own IP. For example "1.2.3.4" > The value `"0.0.0.0/0"` in `metrics_whitelist` opens access to metrics from any IP address. It is recommended to replace it with your personal IP, for example: `"1.2.3.4/32"`.
## Additional parameters ## Additional parameters
### Domain in link instead of IP ### Domain in the link instead of IP
To specify a domain in the links, add to the `[general.links]` section of the config file. To display a domain instead of an IP address in the connection links, add the following lines to the configuration file:
```toml ```toml
[general.links] [general.links]
public_host = "proxy.example.com" public_host = "proxy.example.com"
``` ```
### Server connection limit ### Total server connection limit
Limits the total number of open connections to the server: This parameter limits the total number of active connections to the server:
```toml ```toml
[server] [server]
max_connections = 10000 # 0 - unlimited, 10000 - default max_connections = 10000 # 0 - unlimited, 10000 - default
``` ```
### Upstream Manager ### Upstream Manager
To specify an upstream, add to the `[[upstreams]]` section of the config.toml file: To configure outbound connections (upstreams), add the corresponding parameters to the `[[upstreams]]` section of the configuration file:
#### Binding to IP
#### Binding to an outbound IP address
```toml ```toml
[[upstreams]] [[upstreams]]
type = "direct" type = "direct"
weight = 1 weight = 1
enabled = true enabled = true
interface = "192.168.1.100" # Change to your outgoing IP interface = "192.168.1.100" # Replace with your outbound IP
``` ```
#### SOCKS4/5 as Upstream
- Without authentication: #### Using SOCKS4/5 as an Upstream
- Without authorization:
```toml ```toml
[[upstreams]] [[upstreams]]
type = "socks5" # Specify SOCKS4 or SOCKS5 type = "socks5" # Specify SOCKS4 or SOCKS5
@@ -116,7 +125,7 @@ weight = 1 # Set Weight for Scenarios
enabled = true enabled = true
``` ```
- With authentication: - With authorization:
```toml ```toml
[[upstreams]] [[upstreams]]
type = "socks5" # Specify SOCKS4 or SOCKS5 type = "socks5" # Specify SOCKS4 or SOCKS5
@@ -127,8 +136,8 @@ weight = 1 # Set Weight for Scenarios
enabled = true enabled = true
``` ```
#### Shadowsocks as Upstream #### Using Shadowsocks as an Upstream
Requires `use_middle_proxy = false`. For this method to work, the `use_middle_proxy = false` parameter must be set.
```toml ```toml
[general] [general]

View File

@@ -1,32 +1,32 @@
## Как настроить канал "спонсор прокси" и статистику через бота @MTProxybot ## Как настроить канал "спонсор прокси" и статистику через бота @MTProxybot
1. Зайти в бота @MTProxybot. 1. Зайдите в бота @MTProxybot.
2. Ввести команду `/newproxy` 2. Введите команду `/newproxy`.
3. Отправить IP и порт сервера. Например: 1.2.3.4:443 3. Отправьте IP-адрес и порт сервера. Например: `1.2.3.4:443`.
4. Открыть конфиг `nano /etc/telemt/telemt.toml`. 4. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`.
5. Скопировать и отправить боту секрет пользователя из раздела [access.users]. 5. Скопируйте и отправьте боту секрет пользователя из раздела `[access.users]`.
6. Скопировать полученный tag у бота. Например 1234567890abcdef1234567890abcdef. 6. Скопируйте тег (tag), который выдаст бот. Например: `1234567890abcdef1234567890abcdef`.
> [!WARNING] > [!WARNING]
> Ссылка, которую выдает бот, не будет работать. Не копируйте и не используйте её! > Ссылка, которую выдает бот, работать не будет. Не копируйте и не используйте её!
7. Раскомментировать параметр ad_tag и вписать tag, полученный у бота. 7. Раскомментируйте параметр `ad_tag` и впишите тег, полученный от бота.
8. Раскомментировать/добавить параметр use_middle_proxy = true. 8. Раскомментируйте или добавьте параметр `use_middle_proxy = true`.
Пример конфига: Пример конфигурации:
```toml ```toml
[general] [general]
ad_tag = "1234567890abcdef1234567890abcdef" ad_tag = "1234567890abcdef1234567890abcdef"
use_middle_proxy = true use_middle_proxy = true
``` ```
9. Сохранить конфиг. Ctrl+S -> Ctrl+X. 9. Сохраните изменения (в nano: Ctrl+S -> Ctrl+X).
10. Перезапустить telemt `systemctl restart telemt`. 10. Перезапустите службу telemt: `systemctl restart telemt`.
11. В боте отправить команду /myproxies и выбрать добавленный сервер. 11. В боте отправьте команду `/myproxies` и выберите добавленный сервер.
12. Нажать кнопку "Set promotion". 12. Нажмите кнопку «Set promotion».
13. Отправить **публичную ссылку** на канал. Приватный канал добавить нельзя! 13. Отправьте **публичную ссылку** на канал. Приватные каналы добавлять нельзя!
14. Подождать примерно 1 час, пока информация обновится на серверах Telegram. 14. Подождите примерно 1 час, пока информация обновится на серверах Telegram.
> [!WARNING] > [!WARNING]
> У вас не будет отображаться "спонсор прокси" если вы уже подписаны на канал. > Спонсорский канал не будет у вас отображаться, если вы уже на него подписаны.
**Также вы можете настроить разные каналы для разных пользователей.** **Вы также можете настроить разные спонсорские каналы для разных пользователей:**
```toml ```toml
[access.user_ad_tags] [access.user_ad_tags]
hello = "ad_tag" hello = "ad_tag"
@@ -37,77 +37,85 @@ hello2 = "ad_tag2"
https://github.com/telemt/telemt/discussions/167 https://github.com/telemt/telemt/discussions/167
## Сколько человек может пользоваться 1 ссылкой ## Сколько человек может пользоваться одной ссылкой
По умолчанию 1 ссылкой может пользоваться сколько угодно человек. По умолчанию одной ссылкой может пользоваться неограниченное число людей.
Вы можете ограничить число IP, использующих прокси. Однако вы можете ограничить количество уникальных IP-адресов для каждого пользователя:
```toml ```toml
[access.user_max_unique_ips] [access.user_max_unique_ips]
hello = 1 hello = 1
``` ```
Этот параметр ограничивает, сколько уникальных IP может использовать 1 ссылку одновременно. Если один пользователь отключится, второй сможет подключиться. Также с одного IP может сидеть несколько пользователей. Этот параметр задает максимальное количество уникальных IP-адресов, с которых можно одновременно использовать одну ссылку. Если первый пользователь отключится, второй сможет подключиться. При этом с одного IP-адреса могут подключаться несколько пользователей одновременно (например, устройства в одной Wi-Fi сети).
## Как сделать несколько разных ссылок ## Как создать несколько разных ссылок
1. Сгенерируйте нужное число секретов `openssl rand -hex 16` 1. Сгенерируйте необходимое количество секретов с помощью команды: `openssl rand -hex 16`.
2. Открыть конфиг `nano /etc/telemt.toml` 2. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`.
3. Добавить новых пользователей. 3. Добавьте новых пользователей в секцию `[access.users]`:
```toml ```toml
[access.users] [access.users]
user1 = "00000000000000000000000000000001" user1 = "00000000000000000000000000000001"
user2 = "00000000000000000000000000000002" user2 = "00000000000000000000000000000002"
user3 = "00000000000000000000000000000003" user3 = "00000000000000000000000000000003"
``` ```
4. Сохранить конфиг. Ctrl+S -> Ctrl+X. Перезапускать telemt не нужно. 4. Сохраните конфигурацию (Ctrl+S -> Ctrl+X). Перезапускать службу telemt не нужно.
5. Получить ссылки через 5. Получите готовые ссылки с помощью команды:
```bash ```bash
curl -s http://127.0.0.1:9091/v1/users | jq curl -s http://127.0.0.1:9091/v1/users | jq
``` ```
## Ошибка "Unknown TLS SNI" ## Ошибка "Unknown TLS SNI"
Возможно, вы обновили tls_domain, но пользователи всё ещё пытаются подключаться по старым ссылкам с прежним доменом. Обычно эта ошибка возникает, если вы изменили параметр `tls_domain`, но пользователи продолжают подключаться по старым ссылкам с прежним доменом.
Если необходимо разрешить подключение с любыми доменами (игнорируя несовпадения SNI), добавьте следующие параметры:
```toml
[censorship]
unknown_sni_action = "mask"
```
## Как посмотреть метрики ## Как посмотреть метрики
1. Открыть конфиг `nano /etc/telemt/telemt.toml` 1. Откройте файл конфигурации: `nano /etc/telemt/telemt.toml`.
2. Добавить следующие параметры 2. Добавьте следующие параметры:
```toml ```toml
[server] [server]
metrics_port = 9090 metrics_port = 9090
metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"] metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"]
``` ```
3. Сохранить конфиг. Ctrl+S -> Ctrl+X. 3. Сохраните изменения (Ctrl+S -> Ctrl+X).
4. Метрики доступны по адресу SERVER_IP:9090/metrics. 4. После этого метрики будут доступны по адресу: `SERVER_IP:9090/metrics`.
> [!WARNING] > [!WARNING]
> "0.0.0.0/0" в metrics_whitelist открывает доступ с любого IP. Замените на свой ip. Например "1.2.3.4" > Значение `"0.0.0.0/0"` в `metrics_whitelist` открывает доступ к метрикам с любого IP-адреса. Рекомендуется заменить его на ваш личный IP, например: `"1.2.3.4/32"`.
## Дополнительные параметры ## Дополнительные параметры
### Домен в ссылке вместо IP ### Домен в ссылке вместо IP
Чтобы указать домен в ссылках, добавьте в секцию `[general.links]` файла config. Чтобы в ссылках для подключения отображался домен вместо IP-адреса, добавьте следующие строки в файл конфигурации:
```toml ```toml
[general.links] [general.links]
public_host = "proxy.example.com" public_host = "proxy.example.com"
``` ```
### Общий лимит подключений к серверу ### Общий лимит подключений к серверу
Ограничивает общее число открытых подключений к серверу: Этот параметр ограничивает общее количество активных подключений к серверу:
```toml ```toml
[server] [server]
max_connections = 10000 # 0 - unlimited, 10000 - default max_connections = 10000 # 0 - без ограничений, 10000 - по умолчанию
``` ```
### Upstream Manager ### Upstream Manager
Чтобы указать апстрим, добавьте в секцию `[[upstreams]]` файла config.toml: Для настройки исходящих подключений (апстримов) добавьте соответствующие параметры в секцию `[[upstreams]]` файла конфигурации:
#### Привязка к IP
#### Привязка к исходящему IP-адресу
```toml ```toml
[[upstreams]] [[upstreams]]
type = "direct" type = "direct"
weight = 1 weight = 1
enabled = true enabled = true
interface = "192.168.1.100" # Change to your outgoing IP interface = "192.168.1.100" # Замените на ваш исходящий IP
``` ```
#### SOCKS4/5 как Upstream
#### Использование SOCKS4/5 в качестве Upstream
- Без авторизации: - Без авторизации:
```toml ```toml
[[upstreams]] [[upstreams]]
@@ -128,8 +136,8 @@ weight = 1 # Set Weight for Scenarios
enabled = true enabled = true
``` ```
#### Shadowsocks как Upstream #### Использование Shadowsocks в качестве Upstream
Требует `use_middle_proxy = false`. Для работы этого метода требуется установить параметр `use_middle_proxy = false`.
```toml ```toml
[general] [general]

View File

@@ -37,12 +37,12 @@ mod runtime_watch;
mod runtime_zero; mod runtime_zero;
mod users; mod users;
use config_store::{current_revision, parse_if_match}; use config_store::{current_revision, load_config_from_disk, parse_if_match};
use events::ApiEventStore; use events::ApiEventStore;
use http_utils::{error_response, read_json, read_optional_json, success_response}; use http_utils::{error_response, read_json, read_optional_json, success_response};
use model::{ use model::{
ApiFailure, CreateUserRequest, HealthData, PatchUserRequest, RotateSecretRequest, SummaryData, ApiFailure, CreateUserRequest, DeleteUserResponse, HealthData, PatchUserRequest,
UserActiveIps, RotateSecretRequest, SummaryData, UserActiveIps,
}; };
use runtime_edge::{ use runtime_edge::{
EdgeConnectionsCacheEntry, build_runtime_connections_summary_data, EdgeConnectionsCacheEntry, build_runtime_connections_summary_data,
@@ -380,13 +380,16 @@ async fn handle(
} }
("GET", "/v1/stats/users") | ("GET", "/v1/users") => { ("GET", "/v1/stats/users") | ("GET", "/v1/users") => {
let revision = current_revision(&shared.config_path).await?; let revision = current_revision(&shared.config_path).await?;
let disk_cfg = load_config_from_disk(&shared.config_path).await?;
let runtime_cfg = config_rx.borrow().clone();
let (detected_ip_v4, detected_ip_v6) = shared.detected_link_ips(); let (detected_ip_v4, detected_ip_v6) = shared.detected_link_ips();
let users = users_from_config( let users = users_from_config(
&cfg, &disk_cfg,
&shared.stats, &shared.stats,
&shared.ip_tracker, &shared.ip_tracker,
detected_ip_v4, detected_ip_v4,
detected_ip_v6, detected_ip_v6,
Some(runtime_cfg.as_ref()),
) )
.await; .await;
Ok(success_response(StatusCode::OK, users, revision)) Ok(success_response(StatusCode::OK, users, revision))
@@ -405,7 +408,7 @@ async fn handle(
let expected_revision = parse_if_match(req.headers()); let expected_revision = parse_if_match(req.headers());
let body = read_json::<CreateUserRequest>(req.into_body(), body_limit).await?; let body = read_json::<CreateUserRequest>(req.into_body(), body_limit).await?;
let result = create_user(body, expected_revision, &shared).await; let result = create_user(body, expected_revision, &shared).await;
let (data, revision) = match result { let (mut data, revision) = match result {
Ok(ok) => ok, Ok(ok) => ok,
Err(error) => { Err(error) => {
shared shared
@@ -414,11 +417,18 @@ async fn handle(
return Err(error); return Err(error);
} }
}; };
let runtime_cfg = config_rx.borrow().clone();
data.user.in_runtime = runtime_cfg.access.users.contains_key(&data.user.username);
shared.runtime_events.record( shared.runtime_events.record(
"api.user.create.ok", "api.user.create.ok",
format!("username={}", data.user.username), format!("username={}", data.user.username),
); );
Ok(success_response(StatusCode::CREATED, data, revision)) let status = if data.user.in_runtime {
StatusCode::CREATED
} else {
StatusCode::ACCEPTED
};
Ok(success_response(status, data, revision))
} }
_ => { _ => {
if let Some(user) = path.strip_prefix("/v1/users/") if let Some(user) = path.strip_prefix("/v1/users/")
@@ -427,13 +437,16 @@ async fn handle(
{ {
if method == Method::GET { if method == Method::GET {
let revision = current_revision(&shared.config_path).await?; let revision = current_revision(&shared.config_path).await?;
let disk_cfg = load_config_from_disk(&shared.config_path).await?;
let runtime_cfg = config_rx.borrow().clone();
let (detected_ip_v4, detected_ip_v6) = shared.detected_link_ips(); let (detected_ip_v4, detected_ip_v6) = shared.detected_link_ips();
let users = users_from_config( let users = users_from_config(
&cfg, &disk_cfg,
&shared.stats, &shared.stats,
&shared.ip_tracker, &shared.ip_tracker,
detected_ip_v4, detected_ip_v4,
detected_ip_v6, detected_ip_v6,
Some(runtime_cfg.as_ref()),
) )
.await; .await;
if let Some(user_info) = if let Some(user_info) =
@@ -461,7 +474,7 @@ async fn handle(
let body = let body =
read_json::<PatchUserRequest>(req.into_body(), body_limit).await?; read_json::<PatchUserRequest>(req.into_body(), body_limit).await?;
let result = patch_user(user, body, expected_revision, &shared).await; let result = patch_user(user, body, expected_revision, &shared).await;
let (data, revision) = match result { let (mut data, revision) = match result {
Ok(ok) => ok, Ok(ok) => ok,
Err(error) => { Err(error) => {
shared.runtime_events.record( shared.runtime_events.record(
@@ -471,10 +484,17 @@ async fn handle(
return Err(error); return Err(error);
} }
}; };
let runtime_cfg = config_rx.borrow().clone();
data.in_runtime = runtime_cfg.access.users.contains_key(&data.username);
shared shared
.runtime_events .runtime_events
.record("api.user.patch.ok", format!("username={}", data.username)); .record("api.user.patch.ok", format!("username={}", data.username));
return Ok(success_response(StatusCode::OK, data, revision)); let status = if data.in_runtime {
StatusCode::OK
} else {
StatusCode::ACCEPTED
};
return Ok(success_response(status, data, revision));
} }
if method == Method::DELETE { if method == Method::DELETE {
if api_cfg.read_only { if api_cfg.read_only {
@@ -502,7 +522,18 @@ async fn handle(
shared shared
.runtime_events .runtime_events
.record("api.user.delete.ok", format!("username={}", deleted_user)); .record("api.user.delete.ok", format!("username={}", deleted_user));
return Ok(success_response(StatusCode::OK, deleted_user, revision)); let runtime_cfg = config_rx.borrow().clone();
let in_runtime = runtime_cfg.access.users.contains_key(&deleted_user);
let response = DeleteUserResponse {
username: deleted_user,
in_runtime,
};
let status = if response.in_runtime {
StatusCode::ACCEPTED
} else {
StatusCode::OK
};
return Ok(success_response(status, response, revision));
} }
if method == Method::POST if method == Method::POST
&& let Some(base_user) = user.strip_suffix("/rotate-secret") && let Some(base_user) = user.strip_suffix("/rotate-secret")
@@ -530,7 +561,7 @@ async fn handle(
&shared, &shared,
) )
.await; .await;
let (data, revision) = match result { let (mut data, revision) = match result {
Ok(ok) => ok, Ok(ok) => ok,
Err(error) => { Err(error) => {
shared.runtime_events.record( shared.runtime_events.record(
@@ -540,11 +571,19 @@ async fn handle(
return Err(error); return Err(error);
} }
}; };
let runtime_cfg = config_rx.borrow().clone();
data.user.in_runtime =
runtime_cfg.access.users.contains_key(&data.user.username);
shared.runtime_events.record( shared.runtime_events.record(
"api.user.rotate_secret.ok", "api.user.rotate_secret.ok",
format!("username={}", base_user), format!("username={}", base_user),
); );
return Ok(success_response(StatusCode::OK, data, revision)); let status = if data.user.in_runtime {
StatusCode::OK
} else {
StatusCode::ACCEPTED
};
return Ok(success_response(status, data, revision));
} }
if method == Method::POST { if method == Method::POST {
return Ok(error_response( return Ok(error_response(

View File

@@ -428,6 +428,7 @@ pub(super) struct UserLinks {
#[derive(Serialize)] #[derive(Serialize)]
pub(super) struct UserInfo { pub(super) struct UserInfo {
pub(super) username: String, pub(super) username: String,
pub(super) in_runtime: bool,
pub(super) user_ad_tag: Option<String>, pub(super) user_ad_tag: Option<String>,
pub(super) max_tcp_conns: Option<usize>, pub(super) max_tcp_conns: Option<usize>,
pub(super) expiration_rfc3339: Option<String>, pub(super) expiration_rfc3339: Option<String>,
@@ -454,6 +455,12 @@ pub(super) struct CreateUserResponse {
pub(super) secret: String, pub(super) secret: String,
} }
#[derive(Serialize)]
pub(super) struct DeleteUserResponse {
pub(super) username: String,
pub(super) in_runtime: bool,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub(super) struct CreateUserRequest { pub(super) struct CreateUserRequest {
pub(super) username: String, pub(super) username: String,

View File

@@ -136,6 +136,7 @@ pub(super) async fn create_user(
&shared.ip_tracker, &shared.ip_tracker,
detected_ip_v4, detected_ip_v4,
detected_ip_v6, detected_ip_v6,
None,
) )
.await; .await;
let user = users let user = users
@@ -143,6 +144,7 @@ pub(super) async fn create_user(
.find(|entry| entry.username == body.username) .find(|entry| entry.username == body.username)
.unwrap_or(UserInfo { .unwrap_or(UserInfo {
username: body.username.clone(), username: body.username.clone(),
in_runtime: false,
user_ad_tag: None, user_ad_tag: None,
max_tcp_conns: cfg max_tcp_conns: cfg
.access .access
@@ -243,6 +245,7 @@ pub(super) async fn patch_user(
&shared.ip_tracker, &shared.ip_tracker,
detected_ip_v4, detected_ip_v4,
detected_ip_v6, detected_ip_v6,
None,
) )
.await; .await;
let user_info = users let user_info = users
@@ -300,6 +303,7 @@ pub(super) async fn rotate_secret(
&shared.ip_tracker, &shared.ip_tracker,
detected_ip_v4, detected_ip_v4,
detected_ip_v6, detected_ip_v6,
None,
) )
.await; .await;
let user_info = users let user_info = users
@@ -372,6 +376,7 @@ pub(super) async fn users_from_config(
ip_tracker: &UserIpTracker, ip_tracker: &UserIpTracker,
startup_detected_ip_v4: Option<IpAddr>, startup_detected_ip_v4: Option<IpAddr>,
startup_detected_ip_v6: Option<IpAddr>, startup_detected_ip_v6: Option<IpAddr>,
runtime_cfg: Option<&ProxyConfig>,
) -> Vec<UserInfo> { ) -> Vec<UserInfo> {
let mut names = cfg.access.users.keys().cloned().collect::<Vec<_>>(); let mut names = cfg.access.users.keys().cloned().collect::<Vec<_>>();
names.sort(); names.sort();
@@ -401,6 +406,9 @@ pub(super) async fn users_from_config(
tls: Vec::new(), tls: Vec::new(),
}); });
users.push(UserInfo { users.push(UserInfo {
in_runtime: runtime_cfg
.map(|runtime| runtime.access.users.contains_key(&username))
.unwrap_or(false),
user_ad_tag: cfg.access.user_ad_tags.get(&username).cloned(), user_ad_tag: cfg.access.user_ad_tags.get(&username).cloned(),
max_tcp_conns: cfg max_tcp_conns: cfg
.access .access
@@ -605,35 +613,75 @@ mod tests {
let stats = Stats::new(); let stats = Stats::new();
let tracker = UserIpTracker::new(); let tracker = UserIpTracker::new();
let users = users_from_config(&cfg, &stats, &tracker, None, None).await; let users = users_from_config(&cfg, &stats, &tracker, None, None, None).await;
let alice = users let alice = users
.iter() .iter()
.find(|entry| entry.username == "alice") .find(|entry| entry.username == "alice")
.expect("alice must be present"); .expect("alice must be present");
assert!(!alice.in_runtime);
assert_eq!(alice.max_tcp_conns, Some(7)); assert_eq!(alice.max_tcp_conns, Some(7));
cfg.access.user_max_tcp_conns.insert("alice".to_string(), 5); cfg.access.user_max_tcp_conns.insert("alice".to_string(), 5);
let users = users_from_config(&cfg, &stats, &tracker, None, None).await; let users = users_from_config(&cfg, &stats, &tracker, None, None, None).await;
let alice = users let alice = users
.iter() .iter()
.find(|entry| entry.username == "alice") .find(|entry| entry.username == "alice")
.expect("alice must be present"); .expect("alice must be present");
assert!(!alice.in_runtime);
assert_eq!(alice.max_tcp_conns, Some(5)); assert_eq!(alice.max_tcp_conns, Some(5));
cfg.access.user_max_tcp_conns.insert("alice".to_string(), 0); cfg.access.user_max_tcp_conns.insert("alice".to_string(), 0);
let users = users_from_config(&cfg, &stats, &tracker, None, None).await; let users = users_from_config(&cfg, &stats, &tracker, None, None, None).await;
let alice = users let alice = users
.iter() .iter()
.find(|entry| entry.username == "alice") .find(|entry| entry.username == "alice")
.expect("alice must be present"); .expect("alice must be present");
assert!(!alice.in_runtime);
assert_eq!(alice.max_tcp_conns, Some(7)); assert_eq!(alice.max_tcp_conns, Some(7));
cfg.access.user_max_tcp_conns_global_each = 0; cfg.access.user_max_tcp_conns_global_each = 0;
let users = users_from_config(&cfg, &stats, &tracker, None, None).await; let users = users_from_config(&cfg, &stats, &tracker, None, None, None).await;
let alice = users let alice = users
.iter() .iter()
.find(|entry| entry.username == "alice") .find(|entry| entry.username == "alice")
.expect("alice must be present"); .expect("alice must be present");
assert!(!alice.in_runtime);
assert_eq!(alice.max_tcp_conns, None); assert_eq!(alice.max_tcp_conns, None);
} }
#[tokio::test]
async fn users_from_config_marks_runtime_membership_when_snapshot_is_provided() {
let mut disk_cfg = ProxyConfig::default();
disk_cfg.access.users.insert(
"alice".to_string(),
"0123456789abcdef0123456789abcdef".to_string(),
);
disk_cfg.access.users.insert(
"bob".to_string(),
"fedcba9876543210fedcba9876543210".to_string(),
);
let mut runtime_cfg = ProxyConfig::default();
runtime_cfg.access.users.insert(
"alice".to_string(),
"0123456789abcdef0123456789abcdef".to_string(),
);
let stats = Stats::new();
let tracker = UserIpTracker::new();
let users =
users_from_config(&disk_cfg, &stats, &tracker, None, None, Some(&runtime_cfg)).await;
let alice = users
.iter()
.find(|entry| entry.username == "alice")
.expect("alice must be present");
let bob = users
.iter()
.find(|entry| entry.username == "bob")
.expect("bob must be present");
assert!(alice.in_runtime);
assert!(!bob.in_runtime);
}
} }

View File

@@ -562,9 +562,10 @@ async fn timing_classifier_light_fuzz_pairwise_bucketed_accuracy_stays_bounded_u
if low_info_pair_count > 0 { if low_info_pair_count > 0 {
let low_info_baseline_avg = low_info_baseline_sum / low_info_pair_count as f64; let low_info_baseline_avg = low_info_baseline_sum / low_info_pair_count as f64;
let low_info_hardened_avg = low_info_hardened_sum / low_info_pair_count as f64; let low_info_hardened_avg = low_info_hardened_sum / low_info_pair_count as f64;
let low_info_avg_jitter_budget = 0.40 + acc_quant_step;
assert!( assert!(
low_info_hardened_avg <= low_info_baseline_avg + 0.40, low_info_hardened_avg <= low_info_baseline_avg + low_info_avg_jitter_budget,
"normalization low-info average drift exceeded jitter budget: baseline_avg={low_info_baseline_avg:.3} hardened_avg={low_info_hardened_avg:.3}" "normalization low-info average drift exceeded jitter budget: baseline_avg={low_info_baseline_avg:.3} hardened_avg={low_info_hardened_avg:.3} tolerated={low_info_avg_jitter_budget:.3}"
); );
} }