mirror of
https://github.com/telemt/telemt.git
synced 2026-05-22 19:51:43 +03:00
Compare commits
33 Commits
57729f9e0e
...
456bcb9d61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
456bcb9d61 | ||
|
|
45dd7485a9 | ||
|
|
901cf11c51 | ||
|
|
227a64ef06 | ||
|
|
17fd01a2c4 | ||
|
|
8ed43a562c | ||
|
|
fd6243b6cc | ||
|
|
44127c6f96 | ||
|
|
a0c7a9e62c | ||
|
|
d7af1cc206 | ||
|
|
f8e22970c1 | ||
|
|
792f626336 | ||
|
|
c5c98bb7fa | ||
|
|
6102280345 | ||
|
|
177f0f0325 | ||
|
|
abcce12368 | ||
|
|
31cbf31491 | ||
|
|
f479ecd1ad | ||
|
|
5c953eb4ba | ||
|
|
3771eb4ab2 | ||
|
|
07d19027f6 | ||
|
|
79f4ff4eec | ||
|
|
e6c64525e3 | ||
|
|
59df74e341 | ||
|
|
21a33e4d2a | ||
|
|
73bf23eb61 | ||
|
|
4a904568da | ||
|
|
06eb112efd | ||
|
|
265478b9ca | ||
|
|
038f688e75 | ||
|
|
fa3a1b4dbc | ||
|
|
e2e8b54f87 | ||
|
|
45c66bc823 |
16
.github/FUNDING.yml
vendored
Normal file
16
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom:
|
||||
- https://nowpayments.io/donation?api_key=2bf1afd2-abc2-49f9-a012-f1e715b37223
|
||||
@@ -3,50 +3,39 @@
|
||||
## Purpose
|
||||
|
||||
**Telemt exists to solve technical problems.**
|
||||
- Telemt is open to contributors who want to learn, improve and build meaningful systems together.
|
||||
- It is a place for building, testing, reasoning, documenting, and improving systems.
|
||||
- Discussions that advance this work are in scope, discussions that divert it are not.
|
||||
- Technology has consequences, responsibility is inherent.
|
||||
|
||||
Telemt is open to contributors who want to learn, improve and build meaningful systems together.
|
||||
> **Absicht bestimmt die Form**
|
||||
|
||||
It is a place for building, testing, reasoning, documenting, and improving systems.
|
||||
|
||||
Discussions that advance this work are in scope. Discussions that divert it are not.
|
||||
|
||||
Technology has consequences. Responsibility is inherent.
|
||||
|
||||
> **Zweck bestimmt die Form.**
|
||||
|
||||
> Purpose defines form.
|
||||
> Design follows intent
|
||||
|
||||
---
|
||||
|
||||
## Principles
|
||||
|
||||
* **Technical over emotional**
|
||||
|
||||
Arguments are grounded in data, logs, reproducible cases, or clear reasoning.
|
||||
- Arguments are grounded in data, logs, reproducible cases, or clear reasoning.
|
||||
|
||||
* **Clarity over noise**
|
||||
|
||||
Communication is structured, concise, and relevant.
|
||||
- Communication is structured, concise, and relevant.
|
||||
|
||||
* **Openness with standards**
|
||||
|
||||
Participation is open. The work remains disciplined.
|
||||
- Participation is open. The work remains disciplined.
|
||||
|
||||
* **Independence of judgment**
|
||||
|
||||
Claims are evaluated on technical merit, not affiliation or posture.
|
||||
- Claims are evaluated on technical merit, not affiliation or posture.
|
||||
|
||||
* **Responsibility over capability**
|
||||
|
||||
Capability does not justify careless use.
|
||||
- Capability does not justify careless use.
|
||||
|
||||
* **Cooperation over friction**
|
||||
|
||||
Progress depends on coordination, mutual support, and honest review.
|
||||
- Progress depends on coordination, mutual support, and honest review.
|
||||
|
||||
* **Good intent, rigorous method**
|
||||
|
||||
Assume good intent, but require rigor.
|
||||
- Assume good intent, but require rigor.
|
||||
|
||||
> **Aussagen gelten nach ihrer Begründung.**
|
||||
|
||||
@@ -68,7 +57,9 @@ Participants are expected to:
|
||||
|
||||
Precision is learned.
|
||||
|
||||
New contributors are welcome. They are expected to grow into these standards. Existing contributors are expected to make that growth possible.
|
||||
- New contributors are welcome
|
||||
- They are expected to grow into these standards
|
||||
- Existing contributors are expected to make that growth possible
|
||||
|
||||
> **Wer behauptet, belegt.**
|
||||
|
||||
@@ -112,7 +103,7 @@ Security is both technical and behavioral.
|
||||
|
||||
---
|
||||
|
||||
## 6. Openness
|
||||
## Openness
|
||||
|
||||
Telemt is open to contributors of different backgrounds, experience levels, and working styles.
|
||||
|
||||
@@ -148,10 +139,9 @@ Judgment should be exercised with restraint, consistency, and institutional resp
|
||||
|
||||
All decisions are expected to serve the durability, clarity, and integrity of Telemt.
|
||||
|
||||
> **Ordnung ist Voraussetzung der Funktion.**
|
||||
|
||||
> Order is the precondition of function.
|
||||
> **Klarheit vor Zustimmung - Bestand vor Beifall**
|
||||
|
||||
> Clarity above approval - substantiality before success
|
||||
---
|
||||
|
||||
## Enforcement
|
||||
@@ -171,42 +161,41 @@ Actions are taken to maintain function, continuity, and signal quality.
|
||||
|
||||
## Final
|
||||
|
||||
Telemt is built on discipline, structure, and shared intent.
|
||||
- Signal over noise.
|
||||
- Facts over opinion.
|
||||
- Systems over rhetoric.
|
||||
**Telemt is built on discipline, structure, and shared intent**
|
||||
- Signal over noise
|
||||
- Facts over opinion
|
||||
- Systems over rhetoric
|
||||
- Work is collective
|
||||
- Outcomes are shared
|
||||
- Responsibility is distributed
|
||||
- Precision is learned
|
||||
- Rigor is expected
|
||||
- Help is part of the work
|
||||
|
||||
- Work is collective.
|
||||
- Outcomes are shared.
|
||||
- Responsibility is distributed.
|
||||
> **Ordnung ist Voraussetzung der Freiheit**
|
||||
|
||||
- Precision is learned.
|
||||
- Rigor is expected.
|
||||
- Help is part of the work.
|
||||
|
||||
> **Ordnung ist Voraussetzung der Freiheit.**
|
||||
|
||||
- If you contribute — contribute with care.
|
||||
- If you speak — speak with substance.
|
||||
- If you engage — engage constructively.
|
||||
- If you contribute — contribute with care
|
||||
- If you speak — speak with substance
|
||||
- If you engage — engage constructively
|
||||
|
||||
---
|
||||
|
||||
## After All
|
||||
|
||||
Systems outlive intentions.
|
||||
- What is built will be used.
|
||||
- What is released will propagate.
|
||||
- What is maintained will define the future state.
|
||||
Systems outlive intentions
|
||||
- What is built will be used
|
||||
- What is released will propagate
|
||||
- What is maintained will define the future state
|
||||
|
||||
There is no neutral infrastructure, only infrastructure shaped well or poorly.
|
||||
There is no neutral infrastructure, only infrastructure shaped well or poorly
|
||||
|
||||
> **Jedes System trägt Verantwortung.**
|
||||
> **Ordnung → Umsetzung → Ergebnis**
|
||||
|
||||
> Every system carries responsibility.
|
||||
> Order → Implementation → Result
|
||||
|
||||
- Stability requires discipline.
|
||||
- Freedom requires structure.
|
||||
- Trust requires honesty.
|
||||
- Stability requires discipline
|
||||
- Freedom requires structure
|
||||
- Trust requires honesty
|
||||
|
||||
In the end: the system reflects its contributors
|
||||
|
||||
In the end: the system reflects its contributors.
|
||||
|
||||
84
README.md
84
README.md
@@ -1,30 +1,29 @@
|
||||
# Telemt - MTProxy on Rust + Tokio
|
||||
|
||||
   [](https://t.me/telemtrs)
|
||||
|
||||
***Löst Probleme, bevor andere überhaupt wissen, dass sie existieren*** / ***It solves problems before others even realize they exist***
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Fixed TLS ClientHello is now available:
|
||||
> - in **Telegram Desktop** starting from version **6.7.2**
|
||||
> - in **Telegram Android Client** starting from version **12.6.4**
|
||||
> - **release for iOS is "work in progress"**
|
||||
> Fixed TLS ClientHello is now available in official clients for Desktop / Android / iOS
|
||||
>
|
||||
> To work with EE-MTProxy, please update your client!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://t.me/telemtrs">
|
||||
<img src="/docs/assets/telegram_button.svg" width="200"/>
|
||||
<img src="/docs/assets/telegram_button.svg" width="150"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
**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/Architecture/Model/MODEL.en.md);
|
||||
- [Full-covered API w/ management](https://github.com/telemt/telemt/blob/main/docs/Architecture/API/API.md);
|
||||
- Anti-Replay on Sliding Window;
|
||||
- Prometheus-format Metrics;
|
||||
- TLS-Fronting and TCP-Splicing for masking from "prying" eyes.
|
||||
**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
|
||||
|
||||

|
||||
### One-command Install and Update
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||
```
|
||||
- [Quick Start Guide](docs/Quick_start/QUICK_START_GUIDE.en.md)
|
||||
- [Инструкция по быстрому запуску](docs/Quick_start/QUICK_START_GUIDE.ru.md)
|
||||
|
||||
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](docs/FAQ.en.md#recognizability-for-dpi-and-crawler)
|
||||
|
||||
@@ -40,29 +39,17 @@ Our ***Middle-End Pool*** is fastest by design in standard scenarios, compared t
|
||||
- Graceful shutdown on Ctrl+C;
|
||||
- Extensive logging via `trace` and `debug` with `RUST_LOG` method.
|
||||
|
||||
## One-command installation (update on re-ru)
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||
```
|
||||
See more in the [Quick Start Guide](docs/Quick_start/QUICK_START_GUIDE.en.md).
|
||||
|
||||
# GOTO
|
||||
- [FAQ](#faq)
|
||||
- [Architecture](docs/Architecture)
|
||||
- [Quick Start Guide](#quick-start-guide)
|
||||
- [Config parameters](docs/Config_params)
|
||||
- [Build](#build)
|
||||
- [Why Rust?](#why-rust)
|
||||
|
||||
## Quick Start Guide
|
||||
- [Quick Start Guide RU](docs/Quick_start/QUICK_START_GUIDE.ru.md)
|
||||
- [Quick Start Guide EN](docs/Quick_start/QUICK_START_GUIDE.en.md)
|
||||
|
||||
## FAQ
|
||||
|
||||
- [FAQ RU](docs/FAQ.ru.md)
|
||||
- [FAQ EN](docs/FAQ.en.md)
|
||||
|
||||
# Learn more about Telemt
|
||||
- [Our Architecture](docs/Architecture)
|
||||
- [All Config Options](docs/Config_params)
|
||||
- [How to build your own Telemt?](#build)
|
||||
- [Running on BSD](docs/Quick_start/OPENBSD_QUICK_START_GUIDE.en.md)
|
||||
- [Why Rust?](#why-rust)
|
||||
|
||||
## Build
|
||||
```bash
|
||||
# Cloning repo
|
||||
@@ -72,9 +59,8 @@ cd telemt
|
||||
# Starting Release Build
|
||||
cargo build --release
|
||||
|
||||
# Low-RAM devices (1 GB, e.g. NanoPi Neo3 / Raspberry Pi Zero 2):
|
||||
# release profile uses lto = "thin" to reduce peak linker memory.
|
||||
# If your custom toolchain overrides profiles, avoid enabling fat LTO.
|
||||
# Current release profile uses lto = "fat" for maximum optimization (see Cargo.toml).
|
||||
# On low-RAM systems (~1 GB) you can override it to "thin".
|
||||
|
||||
# Move to /bin
|
||||
mv ./target/release/telemt /bin
|
||||
@@ -84,15 +70,33 @@ chmod +x /bin/telemt
|
||||
telemt config.toml
|
||||
```
|
||||
|
||||
### OpenBSD
|
||||
- Build and service setup guide: [OpenBSD Guide (EN)](docs/Quick_start/OPENBSD_QUICK_START_GUIDE.en.md)
|
||||
- Example rc.d script: [contrib/openbsd/telemt.rcd](contrib/openbsd/telemt.rcd)
|
||||
- Status: OpenBSD sandbox hardening with `pledge(2)` and `unveil(2)` is not implemented yet.
|
||||
|
||||
|
||||
## Why Rust?
|
||||
- Long-running reliability and idempotent behavior
|
||||
- Rust's deterministic resource management - RAII
|
||||
- No garbage collector
|
||||
- Memory safety and reduced attack surface
|
||||
- Tokio's asynchronous architecture
|
||||
|
||||
## Support Telemt
|
||||
|
||||
Telemt is free, open-source, and built in personal time.
|
||||
If it helps you — consider supporting continued development.
|
||||
|
||||
Any cryptocurrency (BTC, ETH, USDT, 350+ coins):
|
||||
|
||||
<p align="center">
|
||||
<a href="https://nowpayments.io/donation?api_key=2bf1afd2-abc2-49f9-a012-f1e715b37223" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://nowpayments.io/images/embeds/donation-button-white.svg" alt="Cryptocurrency & Bitcoin donation button by NOWPayments" height="80">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Monero (XMR) directly:
|
||||
|
||||
```
|
||||
8Bk4tZEYPQWSypeD2hrUXG2rKbAKF16GqEN942ZdAP5cFdSqW6h4DwkP5cJMAdszzuPeHeHZPTyjWWFwzeFdjuci3ktfMoB
|
||||
```
|
||||
|
||||
All donations go toward infrastructure, development, and research.
|
||||
|
||||
|
||||

|
||||
|
||||
56
README.ru.md
56
README.ru.md
@@ -1,30 +1,31 @@
|
||||
# Telemt — MTProxy на Rust + Tokio
|
||||
|
||||
   [](https://t.me/telemtrs)
|
||||
|
||||
***Решает проблемы раньше, чем другие узнают об их существовании***
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Исправленный TLS ClientHello доступен в **Telegram Desktop** начиная с версии **6.7.2**: для работы с EE-MTProxy обновите клиент.
|
||||
>
|
||||
> Исправленный TLS ClientHello доступен в **Telegram Android** начиная с версии **12.6.4**; **официальный релиз для iOS находится в процессе разработки**.
|
||||
> Исправленный TLS ClientHello доступен в Telegram для настольных ПК, Android и iOS.
|
||||
>
|
||||
> Пожалуйста, обновите клиентское приложение для работы с EE-MTProxy.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://t.me/telemtrs">
|
||||
<img src="/docs/assets/telegram_button.svg" width="200"/>
|
||||
<img src="/docs/assets/telegram_button.svg" width="150"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
**Telemt** — это быстрый, безопасный и функциональный сервер, написанный на Rust. Он полностью реализует официальный алгоритм прокси Telegram и добавляет множество улучшений для продакшена:
|
||||
|
||||
- [ME Pool + Reader/Writer + Registry + Refill + Adaptive Floor + Trio-State + жизненный цикл генераций](https://github.com/telemt/telemt/blob/main/docs/Architecture/Model/MODEL.en.md);
|
||||
- [Полноценный API с управлением](https://github.com/telemt/telemt/blob/main/docs/Architecture/API/API.md);
|
||||
- Защита от повторных атак (Anti-Replay on Sliding Window);
|
||||
- Метрики в формате Prometheus;
|
||||
- TLS-fronting и TCP-splicing для маскировки от DPI.
|
||||
## Установка и обновление одной командой
|
||||
|
||||

|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||
```
|
||||
|
||||
## Особенности
|
||||
- [Инструкция по быстрому запуску](docs/Quick_start/QUICK_START_GUIDE.ru.md)
|
||||
- [Quick Start Guide](docs/Quick_start/QUICK_START_GUIDE.en.md)
|
||||
|
||||
Реализация **TLS-fronting** максимально приближена к поведению реального HTTPS-трафика (подробнее - [FAQ](docs/FAQ.ru.md#распознаваемость-для-dpi-и-сканеров)).
|
||||
|
||||
@@ -40,30 +41,15 @@
|
||||
- Корректное завершение работы (Ctrl+C);
|
||||
- Подробное логирование через `trace` и `debug`.
|
||||
|
||||
|
||||
## Быстрая установка (обновление при повторном запуске)
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||
```
|
||||
|
||||
Подробнее об установке в [Quick Start Guide](docs/Quick_start/QUICK_START_GUIDE.ru.md).
|
||||
|
||||
# Навигация
|
||||
# Подробнее о Telemt
|
||||
- [FAQ](#faq)
|
||||
- [Архитектура](docs/Architecture)
|
||||
- [Быстрый старт](#quick-start-guide)
|
||||
- [Параметры конфигурационного файла](docs/Config_params)
|
||||
- [Сборка](#build)
|
||||
- [Установка на BSD](#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%BD%D0%B0-bsd)
|
||||
- [Почему Rust?](#why-rust)
|
||||
- [Известные проблемы](#issues)
|
||||
- [Планы](#roadmap)
|
||||
|
||||
## Быстрый старт
|
||||
- [Quick Start Guide RU](docs/Quick_start/QUICK_START_GUIDE.ru.md)
|
||||
- [Quick Start Guide EN](docs/Quick_start/QUICK_START_GUIDE.en.md)
|
||||
|
||||
## FAQ
|
||||
|
||||
- [FAQ RU](docs/FAQ.ru.md)
|
||||
- [FAQ EN](docs/FAQ.en.md)
|
||||
|
||||
@@ -78,8 +64,8 @@ cd telemt
|
||||
cargo build --release
|
||||
|
||||
# Устройства с небольшим объёмом оперативной памяти (1 ГБ, например NanoPi Neo3 / Raspberry Pi Zero 2):
|
||||
# используется параметр lto = «thin» для уменьшения пикового потребления памяти.
|
||||
# Если ваш пользовательский набор инструментов переопределяет профили, не используйте Fat LTO.
|
||||
# В текущем release-профиле используется lto = "fat" для максимальной оптимизации (см. Cargo.toml).
|
||||
# На системах с малым объёмом RAM (~1 ГБ) можно переопределить это значение на "thin".
|
||||
|
||||
# Перейдите в каталог /bin
|
||||
mv ./target/release/telemt /bin
|
||||
@@ -89,22 +75,16 @@ chmod +x /bin/telemt
|
||||
telemt config.toml
|
||||
```
|
||||
|
||||
### Устройства с малым объемом RAM
|
||||
Для устройств с ~1 ГБ RAM (например Raspberry Pi):
|
||||
- используется облегчённая оптимизация линковщика (thin LTO);
|
||||
- не рекомендуется включать fat LTO.
|
||||
|
||||
## OpenBSD
|
||||
|
||||
## Установка на BSD
|
||||
- Руководство по сборке и настройке на английском языке [OpenBSD Guide (EN)](docs/Quick_start/OPENBSD_QUICK_START_GUIDE.en.md);
|
||||
- Пример rc.d скрипта: [contrib/openbsd/telemt.rcd](contrib/openbsd/telemt.rcd);
|
||||
- Поддержка sandbox с `pledge(2)` и `unveil(2)` пока не реализована.
|
||||
|
||||
## Почему Rust?
|
||||
|
||||
- Надёжность для долгоживущих процессов;
|
||||
- Детерминированное управление ресурсами (RAII);
|
||||
- Отсутствие сборщика мусора;
|
||||
- Безопасность памяти;
|
||||
- Асинхронная архитектура Tokio.
|
||||
|
||||

|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
12
docs/assets/telegram_button.svg
Executable file → Normal file
12
docs/assets/telegram_button.svg
Executable file → Normal file
@@ -1,11 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="250" height="50" viewBox="0 0 250 50">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #1d98dc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect id="Прямоугольник_скругл._углы_1" data-name="Прямоугольник, скругл. углы 1" class="cls-1" width="250" height="50" rx="25" ry="25"/>
|
||||
<image id="Join_us_" data-name="Join us!" x="53" y="12" width="144" height="24" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAAYCAYAAAAVpXQNAAAFnElEQVRoge2aS2hdRRjHf2Pro1aRW1BBpGpLUQRbNRbxsbCSSKWCD7gVFUFEko1gdykaxQeU1NdGEBpEXLjQdOPCLqSh4koXLUhXUk1a8YFQSVqlYm3tJ1/5bpl8mTn33Nxzb25q/nDbnDMzZ+Z88/+ec4KI0EFcDdwM/A183cmJlrBAUAJ16LdOREZEZJ+IfFTRHLtkNtZ0UmoiMm6zTYpI3/+ZoyprJ3vdC5ZXOEcALgT6gacAFfgNwEXAsQrn6QpEZBio21xK1F3AHYvtPapCCGHKeasZ/acKAl0KXAM8CjwNrAMucX3+6fYLL6HjaItAy4DVwHrgceARYIW16YNPAZfbtdJ2arHtZwhhp7mtuq1/qAeW1XNolUCrzDXdbRZng2vfB3wFPAncaPdOAN8vRuGEELb2wDJ6GmUItMJigAeAe4H7jEgxfgXeM/I8B6yN2v5YrARaQnMUEUgD4NuALcDtwK2Zfp8BO4DfgXfNncX4DfhhaS/OC0yZMTmHC9xbqbXZBIwAHwNjwLMZ8hwCtgFPACeB3QnyKL6zX9chIoMiMipzoWn5cFEZIJG2Dif6zCorRPfrIrLXjR9tp+zgU+iCfoOu72CmX5/JYNr1n7b75coWVl9ZLSLPi8gnIvKziJxOCD3GByKy0cZuFpEjmX6nROQVEbmim3UgE87+Ju/QwBxi0AaBEmuMMT1fErnntEWgjFJ5pMZNxvJo1IE+BO4Crk+k3x4HgLeAzy04fsbcVi3TXyvQh4G/WpTXvGGas7+F8SrMs1lXBXOPAkmNN9SsnjTQ7lzzha0xqTQt4mwary5sI3BTE/IoAd4GHgM+tesXgPcLyKM4CkwC/3ZJOLqWcXdbXetAMFgC4MmiJOqvYAm6MRNacIzm2+769C9UVdvkExNcY5qtIYIVS3PKNONvLLfM6jrgYWCz/X2lVZC1nvMN8CrwpdV0VpqgXkrEUB4/WgZ2pjIpFGPQBXljIYRZ9ZsQggphu4jo/6NR06Btfjs4EEIYcPPtNLcVb1y/WfNuo+YUXuWz2633QMHa5hBICXAcOAi8AdwDPAS8Duyxe1usvqPkucqE/nIJ8ih+MivULdSjeWYS2h8LaqcTVBUWKKe5Y+66yGp3Ep4Ag+2eJ6bS+G/t56Ex0ptuk4pw2txXV2DmOXYNE2ZtijARjampazENnC9yFsxX4heEQCoPERmLrKGSZ9LuTXhrVAZlrAgWH420QB7Fn2bZugW/KWWIUOnG5ghbgsjdxPYE0ZVQ440UvpW1lCWQuq5bWnzJoxlL1g4WyvSfN1AyW5w2lFGgUSuBpFxbrAhnx7ZigVqFEuhIxYKf9VL6iUF06bW8DNm8kBbLoW/bihRC0AB6rWVdPlbss3KDx1b7bCc0rFhZAq10p+uvAe/Y3zkcatLeKnyMM8tFmZuISVRUj2kgDpxnHCF7DfG7FQW+LQXFGvNZQrHKJxUWVxZiPgTaZtnZiBUSU1bmZAfSVO+bUwFfnO3UrGiWhPn6vszYXkRM7r5U3SpRLpjTnotxTAG9TJvXq0oeITwoIr+IyDYRWebaNojIFyJyJipzHxeRO9s4shi244jGtS+9T6e0wwTkz3bGY2HruMzz1iSeFaP0WVhGzqWOIgrG+zVr9lSP2uvuqGHOUUb0TnvjsdbWnzj+qWXWMG3zlSbQehHZJCIXZ9p1U3aIyDGbQD9/vKzCM6+sUBKCrjcZm0JSm12/hSaQX08O4+5+ikBlMOrm93JVEpV2YQetEn0y067m70Uzn3vsfOxEq0IqiSENAHNdrZYxkKqaJjBlxxztVqA7DovPmn0VmUrR54PdIYRsETZCrYoT8vi3UkTuF5Fr23zOcEJDCj+/SMHGeI0Uu1dY7+g1CxQ9R12Nt9DnXFKz03gbn5Jv4zOObEXeudG6iPAfDgYBtHqBCrAAAAAASUVORK5CYII="/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 150 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M150,15c0,8.279 -6.721,15 -15,15l-120,0c-8.279,0 -15,-6.721 -15,-15c0,-8.279 6.721,-15 15,-15l120,0c8.279,0 15,6.721 15,15Z" style="fill:#24a1ed;"/><g transform="matrix(20.833333,0,0,20.833333,111.464184,22.329305)"></g><text x="39.666px" y="22.329px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20.833px;fill:#fff;">Join us!</text></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 804 B |
@@ -210,7 +210,7 @@ pub(crate) fn default_proxy_protocol_header_timeout_ms() -> u64 {
|
||||
}
|
||||
|
||||
pub(crate) fn default_proxy_protocol_trusted_cidrs() -> Vec<IpNetwork> {
|
||||
vec!["0.0.0.0/0".parse().unwrap(), "::/0".parse().unwrap()]
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub(crate) fn default_server_max_connections() -> u32 {
|
||||
|
||||
@@ -47,12 +47,18 @@ pub(crate) struct UserAuthEntry {
|
||||
|
||||
impl UserAuthSnapshot {
|
||||
fn from_users(users: &HashMap<String, String>) -> Result<Self> {
|
||||
// Keep runtime user ids stable across reloads so overload scans and
|
||||
// sticky hints do not depend on HashMap iteration order.
|
||||
let mut sorted_users: Vec<_> = users.iter().collect();
|
||||
sorted_users
|
||||
.sort_unstable_by(|(left, _), (right, _)| left.as_bytes().cmp(right.as_bytes()));
|
||||
|
||||
let mut entries = Vec::with_capacity(users.len());
|
||||
let mut by_name = HashMap::with_capacity(users.len());
|
||||
let mut sni_index = HashMap::with_capacity(users.len());
|
||||
let mut sni_initial_index = HashMap::with_capacity(users.len());
|
||||
|
||||
for (user, secret_hex) in users {
|
||||
for (user, secret_hex) in sorted_users {
|
||||
let decoded = hex::decode(secret_hex).map_err(|_| ProxyError::InvalidSecret {
|
||||
user: user.clone(),
|
||||
reason: "Must be 32 hex characters".to_string(),
|
||||
@@ -1734,10 +1740,7 @@ mod tests {
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
cfg_missing.server.proxy_protocol_trusted_cidrs,
|
||||
default_proxy_protocol_trusted_cidrs()
|
||||
);
|
||||
assert!(cfg_missing.server.proxy_protocol_trusted_cidrs.is_empty());
|
||||
|
||||
let cfg_explicit_empty: ProxyConfig = toml::from_str(
|
||||
r#"
|
||||
@@ -1758,6 +1761,46 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_user_auth_snapshot_order_is_stable_across_hashmap_insertion_orders() {
|
||||
let mut left_users = HashMap::new();
|
||||
left_users.insert(
|
||||
"beta".to_string(),
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
|
||||
);
|
||||
left_users.insert(
|
||||
"alpha".to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
|
||||
);
|
||||
|
||||
let mut right_users = HashMap::new();
|
||||
right_users.insert(
|
||||
"alpha".to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
|
||||
);
|
||||
right_users.insert(
|
||||
"beta".to_string(),
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
|
||||
);
|
||||
|
||||
let left_snapshot = UserAuthSnapshot::from_users(&left_users).unwrap();
|
||||
let right_snapshot = UserAuthSnapshot::from_users(&right_users).unwrap();
|
||||
|
||||
let left_names: Vec<_> = left_snapshot
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| entry.user.as_str())
|
||||
.collect();
|
||||
let right_names: Vec<_> = right_snapshot
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| entry.user.as_str())
|
||||
.collect();
|
||||
|
||||
assert_eq!(left_names, ["alpha", "beta"]);
|
||||
assert_eq!(left_names, right_names);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_sni_action_parses_and_defaults_to_drop() {
|
||||
let cfg_default: ProxyConfig = toml::from_str(
|
||||
|
||||
@@ -1363,9 +1363,8 @@ pub struct ServerConfig {
|
||||
|
||||
/// Trusted source CIDRs allowed to send incoming PROXY protocol headers.
|
||||
///
|
||||
/// If this field is omitted in config, it defaults to trust-all CIDRs
|
||||
/// (`0.0.0.0/0` and `::/0`). If it is explicitly set to an empty list,
|
||||
/// all PROXY protocol headers are rejected.
|
||||
/// If this field is omitted in config, it defaults to an empty list and
|
||||
/// all PROXY protocol headers are rejected until trusted CIDRs are set.
|
||||
#[serde(default = "default_proxy_protocol_trusted_cidrs")]
|
||||
pub proxy_protocol_trusted_cidrs: Vec<IpNetwork>,
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ const STICKY_HINT_MAX_ENTRIES: usize = 65_536;
|
||||
const CANDIDATE_HINT_TRACK_CAP: usize = 64;
|
||||
const OVERLOAD_CANDIDATE_BUDGET_HINTED: usize = 16;
|
||||
const OVERLOAD_CANDIDATE_BUDGET_UNHINTED: usize = 8;
|
||||
const OVERLOAD_FULL_SCAN_USER_THRESHOLD: usize = CANDIDATE_HINT_TRACK_CAP;
|
||||
const RECENT_USER_RING_SCAN_LIMIT: usize = 32;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
@@ -242,6 +243,9 @@ fn budget_for_validation(total_users: usize, overload: bool, has_hint: bool) ->
|
||||
if !overload {
|
||||
return total_users;
|
||||
}
|
||||
if total_users <= OVERLOAD_FULL_SCAN_USER_THRESHOLD {
|
||||
return total_users;
|
||||
}
|
||||
let cap = if has_hint {
|
||||
OVERLOAD_CANDIDATE_BUDGET_HINTED
|
||||
} else {
|
||||
@@ -250,6 +254,28 @@ fn budget_for_validation(total_users: usize, overload: bool, has_hint: bool) ->
|
||||
total_users.min(cap.max(1))
|
||||
}
|
||||
|
||||
// Rotate partial overload scans across larger snapshots so one truncated
|
||||
// validation window does not permanently starve the same cold users.
|
||||
fn candidate_scan_start_offset_in(
|
||||
shared: &ProxySharedState,
|
||||
peer_ip: IpAddr,
|
||||
total_users: usize,
|
||||
candidate_budget: usize,
|
||||
) -> usize {
|
||||
if total_users == 0 || candidate_budget >= total_users {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let seq = shared
|
||||
.handshake
|
||||
.auth_candidate_scan_seq
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
let mut hasher = shared.handshake.auth_probe_eviction_hasher.build_hasher();
|
||||
peer_ip.hash(&mut hasher);
|
||||
seq.hash(&mut hasher);
|
||||
hasher.finish() as usize % total_users
|
||||
}
|
||||
|
||||
fn parse_tls_auth_material(
|
||||
handshake: &[u8],
|
||||
ignore_time_skew: bool,
|
||||
@@ -1312,7 +1338,14 @@ where
|
||||
}
|
||||
|
||||
if !matched && !budget_exhausted {
|
||||
for idx in 0..snapshot.entries().len() {
|
||||
let fallback_start = candidate_scan_start_offset_in(
|
||||
shared,
|
||||
peer.ip(),
|
||||
snapshot.entries().len(),
|
||||
candidate_budget,
|
||||
);
|
||||
for offset in 0..snapshot.entries().len() {
|
||||
let idx = (fallback_start + offset) % snapshot.entries().len();
|
||||
let Some(user_id) = u32::try_from(idx).ok() else {
|
||||
break;
|
||||
};
|
||||
@@ -1679,7 +1712,14 @@ where
|
||||
}
|
||||
|
||||
if !matched && !budget_exhausted {
|
||||
for idx in 0..snapshot.entries().len() {
|
||||
let fallback_start = candidate_scan_start_offset_in(
|
||||
shared,
|
||||
peer.ip(),
|
||||
snapshot.entries().len(),
|
||||
candidate_budget,
|
||||
);
|
||||
for offset in 0..snapshot.entries().len() {
|
||||
let idx = (fallback_start + offset) % snapshot.entries().len();
|
||||
let Some(user_id) = u32::try_from(idx).ok() else {
|
||||
break;
|
||||
};
|
||||
|
||||
@@ -506,6 +506,40 @@ fn is_mask_target_local_listener_with_interfaces(
|
||||
local_addr: SocketAddr,
|
||||
resolved_override: Option<SocketAddr>,
|
||||
interface_ips: &[IpAddr],
|
||||
) -> bool {
|
||||
let resolved_candidates = resolved_override
|
||||
.as_ref()
|
||||
.map(std::slice::from_ref)
|
||||
.unwrap_or(&[]);
|
||||
is_mask_target_local_listener_candidates_with_interfaces(
|
||||
mask_host,
|
||||
mask_port,
|
||||
local_addr,
|
||||
resolved_candidates,
|
||||
interface_ips,
|
||||
)
|
||||
}
|
||||
|
||||
fn mask_ip_targets_local_listener(
|
||||
mask_ip: IpAddr,
|
||||
local_ip: IpAddr,
|
||||
interface_ips: &[IpAddr],
|
||||
) -> bool {
|
||||
let mask_ip = canonical_ip(mask_ip);
|
||||
if mask_ip == local_ip {
|
||||
return true;
|
||||
}
|
||||
|
||||
local_ip.is_unspecified()
|
||||
&& (mask_ip.is_loopback() || mask_ip.is_unspecified() || interface_ips.contains(&mask_ip))
|
||||
}
|
||||
|
||||
fn is_mask_target_local_listener_candidates_with_interfaces(
|
||||
mask_host: &str,
|
||||
mask_port: u16,
|
||||
local_addr: SocketAddr,
|
||||
resolved_candidates: &[SocketAddr],
|
||||
interface_ips: &[IpAddr],
|
||||
) -> bool {
|
||||
if mask_port != local_addr.port() {
|
||||
return false;
|
||||
@@ -514,31 +548,14 @@ fn is_mask_target_local_listener_with_interfaces(
|
||||
let local_ip = canonical_ip(local_addr.ip());
|
||||
let literal_mask_ip = parse_mask_host_ip_literal(mask_host).map(canonical_ip);
|
||||
|
||||
if let Some(addr) = resolved_override {
|
||||
let resolved_ip = canonical_ip(addr.ip());
|
||||
if resolved_ip == local_ip {
|
||||
return true;
|
||||
}
|
||||
|
||||
if local_ip.is_unspecified()
|
||||
&& (resolved_ip.is_loopback()
|
||||
|| resolved_ip.is_unspecified()
|
||||
|| interface_ips.contains(&resolved_ip))
|
||||
{
|
||||
for addr in resolved_candidates {
|
||||
if mask_ip_targets_local_listener(addr.ip(), local_ip, interface_ips) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mask_ip) = literal_mask_ip {
|
||||
if mask_ip == local_ip {
|
||||
return true;
|
||||
}
|
||||
|
||||
if local_ip.is_unspecified()
|
||||
&& (mask_ip.is_loopback()
|
||||
|| mask_ip.is_unspecified()
|
||||
|| interface_ips.contains(&mask_ip))
|
||||
{
|
||||
if mask_ip_targets_local_listener(mask_ip, local_ip, interface_ips) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -572,21 +589,67 @@ async fn is_mask_target_local_listener_async(
|
||||
mask_port: u16,
|
||||
local_addr: SocketAddr,
|
||||
resolved_override: Option<SocketAddr>,
|
||||
) -> bool {
|
||||
let resolved_candidates = resolved_override
|
||||
.as_ref()
|
||||
.map(std::slice::from_ref)
|
||||
.unwrap_or(&[]);
|
||||
is_mask_target_local_listener_candidates_async(
|
||||
mask_host,
|
||||
mask_port,
|
||||
local_addr,
|
||||
resolved_candidates,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn is_mask_target_local_listener_candidates_async(
|
||||
mask_host: &str,
|
||||
mask_port: u16,
|
||||
local_addr: SocketAddr,
|
||||
resolved_candidates: &[SocketAddr],
|
||||
) -> bool {
|
||||
if mask_port != local_addr.port() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let interfaces = local_interface_ips_async().await;
|
||||
is_mask_target_local_listener_with_interfaces(
|
||||
is_mask_target_local_listener_candidates_with_interfaces(
|
||||
mask_host,
|
||||
mask_port,
|
||||
local_addr,
|
||||
resolved_override,
|
||||
resolved_candidates,
|
||||
&interfaces,
|
||||
)
|
||||
}
|
||||
|
||||
// Resolve hostnames through the same OS DNS path `TcpStream::connect` uses so
|
||||
// self-target rejection also catches loopback and local-interface hostnames.
|
||||
async fn resolve_mask_target_candidates(
|
||||
mask_host: &str,
|
||||
mask_port: u16,
|
||||
resolved_override: Option<SocketAddr>,
|
||||
) -> Vec<SocketAddr> {
|
||||
if let Some(addr) = resolved_override {
|
||||
return vec![addr];
|
||||
}
|
||||
|
||||
if parse_mask_host_ip_literal(mask_host).is_some() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut resolved = Vec::new();
|
||||
if let Ok(addrs) = tokio::net::lookup_host((mask_host, mask_port)).await {
|
||||
for addr in addrs {
|
||||
if !resolved.contains(&addr) {
|
||||
resolved.push(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolved
|
||||
}
|
||||
|
||||
fn masking_beobachten_ttl(config: &ProxyConfig) -> Duration {
|
||||
let minutes = config.general.beobachten_minutes;
|
||||
let clamped = minutes.clamp(1, 24 * 60);
|
||||
@@ -731,8 +794,15 @@ pub async fn handle_bad_client<R, W>(
|
||||
// Self-referential masking can create recursive proxy loops under
|
||||
// misconfiguration and leak distinguishable load spikes to adversaries.
|
||||
let resolved_mask_addr = resolve_socket_addr(mask_host, mask_port);
|
||||
if is_mask_target_local_listener_async(mask_host, mask_port, local_addr, resolved_mask_addr)
|
||||
.await
|
||||
let resolved_mask_candidates =
|
||||
resolve_mask_target_candidates(mask_host, mask_port, resolved_mask_addr).await;
|
||||
if is_mask_target_local_listener_candidates_async(
|
||||
mask_host,
|
||||
mask_port,
|
||||
local_addr,
|
||||
&resolved_mask_candidates,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let outcome_started = Instant::now();
|
||||
debug!(
|
||||
|
||||
@@ -48,6 +48,7 @@ pub(crate) struct HandshakeSharedState {
|
||||
pub(crate) sticky_user_by_sni_hash: DashMap<u64, u32>,
|
||||
pub(crate) recent_user_ring: Box<[AtomicU32]>,
|
||||
pub(crate) recent_user_ring_seq: AtomicU64,
|
||||
pub(crate) auth_candidate_scan_seq: AtomicU64,
|
||||
pub(crate) auth_expensive_checks_total: AtomicU64,
|
||||
pub(crate) auth_budget_exhausted_total: AtomicU64,
|
||||
}
|
||||
@@ -86,6 +87,7 @@ impl ProxySharedState {
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
recent_user_ring_seq: AtomicU64::new(0),
|
||||
auth_candidate_scan_seq: AtomicU64::new(0),
|
||||
auth_expensive_checks_total: AtomicU64::new(0),
|
||||
auth_budget_exhausted_total: AtomicU64::new(0),
|
||||
},
|
||||
|
||||
@@ -1146,9 +1146,9 @@ async fn tls_overload_budget_limits_candidate_scan_depth() {
|
||||
let mut config = ProxyConfig::default();
|
||||
config.access.users.clear();
|
||||
config.access.ignore_time_skew = true;
|
||||
for idx in 0..32u8 {
|
||||
for idx in 0..96u8 {
|
||||
config.access.users.insert(
|
||||
format!("user-{idx}"),
|
||||
format!("user-{idx:02}"),
|
||||
format!("{:032x}", u128::from(idx) + 1),
|
||||
);
|
||||
}
|
||||
@@ -1203,6 +1203,64 @@ async fn tls_overload_budget_limits_candidate_scan_depth() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tls_overload_full_scans_small_runtime_snapshot_to_preserve_cold_user_auth() {
|
||||
let mut config = ProxyConfig::default();
|
||||
config.access.users.clear();
|
||||
config.access.ignore_time_skew = true;
|
||||
for idx in 0..32u8 {
|
||||
config.access.users.insert(
|
||||
format!("user-{idx:02}"),
|
||||
format!("{:032x}", u128::from(idx) + 1),
|
||||
);
|
||||
}
|
||||
config.rebuild_runtime_user_auth().unwrap();
|
||||
|
||||
let replay_checker = ReplayChecker::new(128, Duration::from_secs(60));
|
||||
let rng = SecureRandom::new();
|
||||
let shared = ProxySharedState::new();
|
||||
let now = Instant::now();
|
||||
{
|
||||
let mut saturation = shared.handshake.auth_probe_saturation.lock().unwrap();
|
||||
*saturation = Some(AuthProbeSaturationState {
|
||||
fail_streak: AUTH_PROBE_BACKOFF_START_FAILS,
|
||||
blocked_until: now + Duration::from_millis(200),
|
||||
last_seen: now,
|
||||
});
|
||||
}
|
||||
|
||||
let peer: SocketAddr = "198.51.100.214:44326".parse().unwrap();
|
||||
let mut secret = [0u8; 16];
|
||||
secret[15] = 32;
|
||||
let handshake = make_valid_tls_handshake(&secret, 0);
|
||||
|
||||
let result = handle_tls_handshake_with_shared(
|
||||
&handshake,
|
||||
tokio::io::empty(),
|
||||
tokio::io::sink(),
|
||||
peer,
|
||||
&config,
|
||||
&replay_checker,
|
||||
&rng,
|
||||
None,
|
||||
shared.as_ref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
matches!(result, HandshakeResult::Success(_)),
|
||||
"overload mode must still authenticate valid cold users when runtime snapshot stays small"
|
||||
);
|
||||
assert_eq!(
|
||||
shared
|
||||
.handshake
|
||||
.auth_expensive_checks_total
|
||||
.load(Ordering::Relaxed),
|
||||
32,
|
||||
"small saturated snapshots must remain fully scannable"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mtproto_runtime_snapshot_prefers_preferred_user_hint() {
|
||||
let mut config = ProxyConfig::default();
|
||||
@@ -1255,6 +1313,66 @@ async fn mtproto_runtime_snapshot_prefers_preferred_user_hint() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mtproto_overload_full_scans_small_runtime_snapshot_to_preserve_cold_user_auth() {
|
||||
let mut config = ProxyConfig::default();
|
||||
config.general.modes.secure = true;
|
||||
config.access.users.clear();
|
||||
config.access.ignore_time_skew = true;
|
||||
for idx in 0..32u8 {
|
||||
config.access.users.insert(
|
||||
format!("user-{idx:02}"),
|
||||
format!("{:032x}", u128::from(idx) + 1),
|
||||
);
|
||||
}
|
||||
config.rebuild_runtime_user_auth().unwrap();
|
||||
|
||||
let shared = ProxySharedState::new();
|
||||
let now = Instant::now();
|
||||
{
|
||||
let mut saturation = shared.handshake.auth_probe_saturation.lock().unwrap();
|
||||
*saturation = Some(AuthProbeSaturationState {
|
||||
fail_streak: AUTH_PROBE_BACKOFF_START_FAILS,
|
||||
blocked_until: now + Duration::from_millis(200),
|
||||
last_seen: now,
|
||||
});
|
||||
}
|
||||
|
||||
let replay_checker = ReplayChecker::new(128, Duration::from_secs(60));
|
||||
let handshake = make_valid_mtproto_handshake(
|
||||
"00000000000000000000000000000020",
|
||||
ProtoTag::Secure,
|
||||
2,
|
||||
);
|
||||
let peer: SocketAddr = "198.51.100.215:44326".parse().unwrap();
|
||||
|
||||
let result = handle_mtproto_handshake_with_shared(
|
||||
&handshake,
|
||||
tokio::io::empty(),
|
||||
tokio::io::sink(),
|
||||
peer,
|
||||
&config,
|
||||
&replay_checker,
|
||||
false,
|
||||
None,
|
||||
shared.as_ref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
matches!(result, HandshakeResult::Success(_)),
|
||||
"overload mode must still authenticate valid direct MTProto users when runtime snapshot stays small"
|
||||
);
|
||||
assert_eq!(
|
||||
shared
|
||||
.handshake
|
||||
.auth_expensive_checks_total
|
||||
.load(Ordering::Relaxed),
|
||||
32,
|
||||
"small saturated MTProto snapshots must remain fully scannable"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn alpn_enforce_rejects_unsupported_client_alpn() {
|
||||
let secret = [0x33u8; 16];
|
||||
|
||||
@@ -88,6 +88,45 @@ async fn self_target_fallback_refuses_recursive_loopback_connect() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn self_target_fallback_refuses_recursive_hostname_connect() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let local_addr = listener.local_addr().unwrap();
|
||||
let accept_task = tokio::spawn(async move {
|
||||
timeout(Duration::from_millis(120), listener.accept())
|
||||
.await
|
||||
.is_ok()
|
||||
});
|
||||
|
||||
let mut config = ProxyConfig::default();
|
||||
config.general.beobachten = false;
|
||||
config.censorship.mask = true;
|
||||
config.censorship.mask_unix_sock = None;
|
||||
config.censorship.mask_host = Some("localhost".to_string());
|
||||
config.censorship.mask_port = local_addr.port();
|
||||
config.censorship.mask_proxy_protocol = 0;
|
||||
|
||||
let peer: SocketAddr = "203.0.113.99:55099".parse().unwrap();
|
||||
let beobachten = BeobachtenStore::new();
|
||||
|
||||
handle_bad_client(
|
||||
tokio::io::empty(),
|
||||
tokio::io::sink(),
|
||||
b"GET /",
|
||||
peer,
|
||||
local_addr,
|
||||
&config,
|
||||
&beobachten,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accepted = accept_task.await.unwrap();
|
||||
assert!(
|
||||
!accepted,
|
||||
"hostname self-target masking must fail closed without connecting to local listener"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn same_ip_different_port_still_forwards_to_mask_backend() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
|
||||
1222
tools/grafana-dashboard-by-user.json
Normal file
1222
tools/grafana-dashboard-by-user.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user