mirror of
https://github.com/telemt/telemt.git
synced 2026-06-19 09:21:10 +03:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5028009aa | |||
| 6c9b5932d8 | |||
| bac9cc01f3 | |||
| d3b0dbd541 | |||
| 5f5a3e3fa0 | |||
| f9e54ee739 | |||
| d477d6ee29 | |||
| 1383dfcbb1 | |||
| 107a7cc758 | |||
| 4f3193fdaa | |||
| d6be691c67 | |||
| 0b0be07a9c | |||
| a728c727bc | |||
| d23ce4a184 | |||
| e48e1b141d | |||
| 82da541f9c | |||
| 7acc76b422 | |||
| b246f0ed99 | |||
| 1265234491 | |||
| 07b53785c5 | |||
| 1e3522652c | |||
| a526fee728 | |||
| 970313edcb |
@@ -25,6 +25,7 @@ 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)
|
||||
|
||||
## Features
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
@@ -54,7 +54,6 @@ curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||
- [FAQ EN](docs/FAQ.en.md)
|
||||
|
||||
## Сборка
|
||||
|
||||
```bash
|
||||
# Клонируйте репозиторий
|
||||
git clone https://github.com/telemt/telemt
|
||||
@@ -63,7 +62,6 @@ cd telemt
|
||||
# Начните процесс сборки
|
||||
cargo build --release
|
||||
|
||||
# Устройства с небольшим объёмом оперативной памяти (1 ГБ, например NanoPi Neo3 / Raspberry Pi Zero 2):
|
||||
# В текущем release-профиле используется lto = "fat" для максимальной оптимизации (см. Cargo.toml).
|
||||
# На системах с малым объёмом RAM (~1 ГБ) можно переопределить это значение на "thin".
|
||||
|
||||
|
||||
+8
-5
@@ -32,13 +32,13 @@ show = "*"
|
||||
port = 443
|
||||
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol
|
||||
# metrics_port = 9090
|
||||
# metrics_listen = "0.0.0.0:9090" # Listen address for metrics (overrides metrics_port)
|
||||
# metrics_whitelist = ["127.0.0.1", "::1", "0.0.0.0/0"]
|
||||
# metrics_listen = "127.0.0.1:9090" # Listen address for metrics (overrides metrics_port)
|
||||
# metrics_whitelist = ["127.0.0.1/32", "::1/128"]
|
||||
|
||||
[server.api]
|
||||
enabled = true
|
||||
listen = "0.0.0.0:9091"
|
||||
whitelist = ["127.0.0.0/8"]
|
||||
listen = "127.0.0.1:9091"
|
||||
whitelist = ["127.0.0.1/32", "::1/128"]
|
||||
minimal_runtime_enabled = false
|
||||
minimal_runtime_cache_ttl_ms = 1000
|
||||
|
||||
@@ -48,9 +48,12 @@ ip = "0.0.0.0"
|
||||
|
||||
# === Anti-Censorship & Masking ===
|
||||
[censorship]
|
||||
# Fake-TLS / SNI masking domain used in generated ee-links.
|
||||
# Changing tls_domain invalidates previously generated TLS links.
|
||||
tls_domain = "petrovich.ru"
|
||||
|
||||
mask = true
|
||||
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
||||
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
||||
tls_front_dir = "tlsfront" # Cache directory for TLS emulation
|
||||
|
||||
[access.users]
|
||||
|
||||
@@ -9,12 +9,12 @@ API runtime is configured in `[server.api]`.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | `bool` | `false` | Enables REST API listener. |
|
||||
| `listen` | `string` (`IP:PORT`) | `127.0.0.1:9091` | API bind address. |
|
||||
| `whitelist` | `CIDR[]` | `127.0.0.1/32, ::1/128` | Source IP allowlist. Empty list means allow all. |
|
||||
| `enabled` | `bool` | `true` | Enables REST API listener. |
|
||||
| `listen` | `string` (`IP:PORT`) | `0.0.0.0:9091` | API bind address. |
|
||||
| `whitelist` | `CIDR[]` | `127.0.0.0/8` | Source IP allowlist. Empty list means allow all. |
|
||||
| `auth_header` | `string` | `""` | Exact value for `Authorization` header. Empty disables header auth. |
|
||||
| `request_body_limit_bytes` | `usize` | `65536` | Maximum request body size. Must be `> 0`. |
|
||||
| `minimal_runtime_enabled` | `bool` | `false` | Enables runtime snapshot endpoints requiring ME pool read-lock aggregation. |
|
||||
| `minimal_runtime_enabled` | `bool` | `true` | Enables runtime snapshot endpoints requiring ME pool read-lock aggregation. |
|
||||
| `minimal_runtime_cache_ttl_ms` | `u64` | `1000` | Cache TTL for minimal snapshots. `0` disables cache; valid range is `[0, 60000]`. |
|
||||
| `runtime_edge_enabled` | `bool` | `false` | Enables runtime edge endpoints with cached aggregation payloads. |
|
||||
| `runtime_edge_cache_ttl_ms` | `u64` | `1000` | Cache TTL for runtime edge summary payloads. `0` disables cache. |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+3072
-3331
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,8 @@ cargo build --release
|
||||
./target/release/telemt --version
|
||||
```
|
||||
|
||||
For low-RAM systems, this repository already uses `lto = "thin"` in release profile.
|
||||
For low-RAM systems, note that this repository currently uses `lto = "fat"` in release profile.
|
||||
On constrained builders, a local override to `lto = "thin"` may be more practical.
|
||||
|
||||
## 3. Install binary and config
|
||||
|
||||
|
||||
@@ -110,15 +110,15 @@ show = "*"
|
||||
# === Server Binding ===
|
||||
[server]
|
||||
port = 443
|
||||
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol
|
||||
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol
|
||||
# metrics_port = 9090
|
||||
# metrics_listen = "0.0.0.0:9090" # Listen address for metrics (overrides metrics_port)
|
||||
# metrics_whitelist = ["127.0.0.1", "::1", "0.0.0.0/0"]
|
||||
# metrics_listen = "127.0.0.1:9090" # Listen address for metrics (overrides metrics_port)
|
||||
# metrics_whitelist = ["127.0.0.1/32", "::1/128"]
|
||||
|
||||
[server.api]
|
||||
enabled = true
|
||||
listen = "0.0.0.0:9091"
|
||||
whitelist = ["127.0.0.0/8"]
|
||||
listen = "127.0.0.1:9091"
|
||||
whitelist = ["127.0.0.1/32", "::1/128"]
|
||||
minimal_runtime_enabled = false
|
||||
minimal_runtime_cache_ttl_ms = 1000
|
||||
|
||||
@@ -128,9 +128,9 @@ ip = "0.0.0.0"
|
||||
|
||||
# === Anti-Censorship & Masking ===
|
||||
[censorship]
|
||||
tls_domain = "petrovich.ru"
|
||||
tls_domain = "petrovich.ru" # Fake-TLS / SNI masking domain used in generated ee-links
|
||||
mask = true
|
||||
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
||||
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
||||
tls_front_dir = "tlsfront" # Cache directory for TLS emulation
|
||||
|
||||
[access.users]
|
||||
@@ -141,9 +141,9 @@ hello = "00000000000000000000000000000000"
|
||||
then Ctrl+S -> Ctrl+X to save
|
||||
|
||||
> [!WARNING]
|
||||
> Replace the value of the hello parameter with the value you obtained in step 0.
|
||||
> Additionally, change the value of the tls_domain parameter to a different website.
|
||||
> Changing the tls_domain parameter will break all links that use the old domain!
|
||||
> Replace the value of the `hello` parameter with the value you obtained in step 0.
|
||||
> Additionally, change the value of the `tls_domain` parameter to a different website.
|
||||
> Changing the `tls_domain` parameter will break all links that use the old domain!
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -103,22 +103,22 @@ tls = true
|
||||
[general.links]
|
||||
show = "*"
|
||||
# show = ["alice", "bob"] # Показывать ссылки только для alice и bob
|
||||
# show = "*" # Показывать ссылки для всех пользователей
|
||||
# public_host = "proxy.example.com" # Хост (IP-адрес или домен) для ссылок tg://
|
||||
# public_port = 443 # Порт для ссылок tg:// (по умолчанию: server.port)
|
||||
# show = "*" # Показывать ссылки для всех пользователей
|
||||
# public_host = "proxy.example.com" # Хост (IP-адрес или домен) для ссылок tg://
|
||||
# public_port = 443 # Порт для ссылок tg:// (по умолчанию: server.port)
|
||||
|
||||
# === Привязка сервера ===
|
||||
[server]
|
||||
port = 443
|
||||
# proxy_protocol = false # Включите, если сервер находится за HAProxy/nginx с протоколом PROXY
|
||||
# proxy_protocol = false # Включите, если сервер находится за HAProxy/nginx с протоколом PROXY
|
||||
# metrics_port = 9090
|
||||
# metrics_listen = "0.0.0.0:9090" # Адрес прослушивания для метрик (переопределяет metrics_port)
|
||||
# metrics_whitelist = ["127.0.0.1", "::1", "0.0.0.0/0"]
|
||||
# metrics_listen = "127.0.0.1:9090" # Адрес прослушивания для метрик (переопределяет metrics_port)
|
||||
# metrics_whitelist = ["127.0.0.1/32", "::1/128"]
|
||||
|
||||
[server.api]
|
||||
enabled = true
|
||||
listen = "0.0.0.0:9091"
|
||||
whitelist = ["127.0.0.0/8"]
|
||||
listen = "127.0.0.1:9091"
|
||||
whitelist = ["127.0.0.1/32", "::1/128"]
|
||||
minimal_runtime_enabled = false
|
||||
minimal_runtime_cache_ttl_ms = 1000
|
||||
|
||||
@@ -128,9 +128,9 @@ ip = "0.0.0.0"
|
||||
|
||||
# === Обход блокировок и маскировка ===
|
||||
[censorship]
|
||||
tls_domain = "petrovich.ru"
|
||||
tls_domain = "petrovich.ru" # Домен Fake-TLS / SNI, который будет использоваться в сгенерированных ee-ссылках
|
||||
mask = true
|
||||
tls_emulation = true # Получить реальную длину сертификата и эмулировать запись TLS
|
||||
tls_emulation = true # Получить реальную длину сертификата и эмулировать запись TLS
|
||||
tls_front_dir = "tlsfront" # Директория кэша для эмуляции TLS
|
||||
|
||||
[access.users]
|
||||
@@ -141,9 +141,9 @@ hello = "00000000000000000000000000000000"
|
||||
Затем нажмите Ctrl+S -> Ctrl+X, чтобы сохранить
|
||||
|
||||
> [!WARNING]
|
||||
> Замените значение параметра hello на значение, которое вы получили в пункте 0.
|
||||
> Так же замените значение параметра tls_domain на другой сайт.
|
||||
> Изменение параметра tls_domain сделает нерабочими все ссылки, использующие старый домен!
|
||||
> Замените значение параметра `hello` на значение, которое вы получили в пункте 0.
|
||||
> Так же замените значение параметра `tls_domain` на другой сайт.
|
||||
> Изменение параметра `tls_domain` сделает нерабочими все ссылки, использующие старый домен!
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
<img src="https://gist.githubusercontent.com/avbor/1f8a128e628f47249aae6e058a57610b/raw/19013276c035e91058e0a9799ab145f8e70e3ff5/scheme.svg">
|
||||
|
||||
## Concept
|
||||
- **Server A** (_e.g., RU_):\
|
||||
Entry point, accepts Telegram proxy user traffic via **Xray** (port `443\tcp`)\
|
||||
and sends it through the tunnel to Server **B**.\
|
||||
Public port for Telegram clients — `443\tcp`
|
||||
- **Server B** (_e.g., NL_):\
|
||||
Exit point, runs the **Xray server** (to terminate the tunnel entry point) and **telemt**.\
|
||||
The server must have unrestricted access to Telegram Data Centers.\
|
||||
Public port for VLESS/REALITY (incoming) — `443\tcp`\
|
||||
Internal telemt port (where decrypted Xray traffic ends up) — `8443\tcp`
|
||||
|
||||
The tunnel works over the `VLESS-XTLS-Reality` (or `VLESS/xhttp/reality`) protocol. The original client IP address is preserved thanks to the PROXYv2 protocol, which Xray on Server A dynamically injects via a local loopback before wrapping the traffic into Reality, transparently delivering the real IPs to telemt on Server B.
|
||||
|
||||
---
|
||||
|
||||
## Step 1. Setup Xray Tunnel (A <-> B)
|
||||
|
||||
You must install **Xray-core** (version 1.8.4 or newer recommended) on both servers.
|
||||
Official installation script (run on both servers):
|
||||
```bash
|
||||
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
|
||||
```
|
||||
|
||||
### Key and Parameter Generation (Run Once)
|
||||
For configuration, you need a unique UUID and Xray Reality keys. Run on any server with Xray installed:
|
||||
1. **Client UUID:**
|
||||
```bash
|
||||
xray uuid
|
||||
# Save the output (e.g.: 12345678-abcd-1234-abcd-1234567890ab) — this is <XRAY_UUID>
|
||||
```
|
||||
2. **X25519 Keypair (Private & Public) for Reality:**
|
||||
```bash
|
||||
xray x25519
|
||||
# Save the Private key (<SERVER_B_PRIVATE_KEY>) and Public key (<SERVER_B_PUBLIC_KEY>)
|
||||
```
|
||||
3. **Short ID (Reality identifier):**
|
||||
```bash
|
||||
openssl rand -hex 16
|
||||
# Save the output (e.g.: 0123456789abcdef0123456789abcdef) — this is <SHORT_ID>
|
||||
```
|
||||
4. **Random Path (for xhttp):**
|
||||
```bash
|
||||
openssl rand -hex 8
|
||||
# Save the output (e.g., abc123def456) to replace <YOUR_RANDOM_PATH> in configs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configuration for Server B (_EU_):
|
||||
|
||||
Create or edit the file `/usr/local/etc/xray/config.json`.
|
||||
This Xray instance will listen on the public `443` port and proxy valid Reality traffic, while routing "disguised" traffic (e.g., direct web browser scans) to `yahoo.com`.
|
||||
|
||||
```bash
|
||||
nano /usr/local/etc/xray/config.json
|
||||
```
|
||||
|
||||
File content:
|
||||
```json
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "error",
|
||||
"access": "none"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "vless-in",
|
||||
"port": 443,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "<XRAY_UUID>"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "xhttp",
|
||||
"security": "reality",
|
||||
"realitySettings": {
|
||||
"dest": "yahoo.com:443",
|
||||
"serverNames": [
|
||||
"yahoo.com"
|
||||
],
|
||||
"privateKey": "<SERVER_B_PRIVATE_KEY>",
|
||||
"shortIds": [
|
||||
"<SHORT_ID>"
|
||||
]
|
||||
},
|
||||
"xhttpSettings": {
|
||||
"path": "/<YOUR_RANDOM_PATH>",
|
||||
"mode": "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "tunnel-to-telemt",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"destination": "127.0.0.1:8443"
|
||||
}
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": [
|
||||
"vless-in"
|
||||
],
|
||||
"outboundTag": "tunnel-to-telemt"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Open the firewall port (if enabled):
|
||||
```bash
|
||||
sudo ufw allow 443/tcp
|
||||
```
|
||||
Restart and setup Xray to run at boot:
|
||||
```bash
|
||||
sudo systemctl restart xray
|
||||
sudo systemctl enable xray
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configuration for Server A (_RU_):
|
||||
|
||||
Similarly, edit `/usr/local/etc/xray/config.json`.
|
||||
Here Xray acts as the public entry point: it listens on `443\tcp`, uses a local loopback (via internal port `10444`) to prepend the `PROXYv2` header, and encapsulates the payload via Reality to Server B, instructing Server B to deliver it to its *local* `127.0.0.1:8443` port (where telemt will listen).
|
||||
|
||||
```bash
|
||||
nano /usr/local/etc/xray/config.json
|
||||
```
|
||||
|
||||
File content:
|
||||
```json
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "error",
|
||||
"access": "none"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "public-in",
|
||||
"port": 443,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 10444,
|
||||
"network": "tcp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "tunnel-in",
|
||||
"port": 10444,
|
||||
"listen": "127.0.0.1",
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 8443,
|
||||
"network": "tcp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "local-injector",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"proxyProtocol": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "vless-out",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"vnext": [
|
||||
{
|
||||
"address": "<PUBLIC_IP_SERVER_B>",
|
||||
"port": 443,
|
||||
"users": [
|
||||
{
|
||||
"id": "<XRAY_UUID>",
|
||||
"encryption": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "xhttp",
|
||||
"security": "reality",
|
||||
"realitySettings": {
|
||||
"serverName": "yahoo.com",
|
||||
"publicKey": "<SERVER_B_PUBLIC_KEY>",
|
||||
"shortId": "<SHORT_ID>",
|
||||
"spiderX": "/",
|
||||
"fingerprint": "chrome"
|
||||
},
|
||||
"xhttpSettings": {
|
||||
"path": "/<YOUR_RANDOM_PATH>"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": ["public-in"],
|
||||
"outboundTag": "local-injector"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": ["tunnel-in"],
|
||||
"outboundTag": "vless-out"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
*Replace `<PUBLIC_IP_SERVER_B>` with the public IP address of Server B.*
|
||||
|
||||
Open the firewall port for clients (if enabled):
|
||||
```bash
|
||||
sudo ufw allow 443/tcp
|
||||
```
|
||||
|
||||
Restart and setup Xray to run at boot:
|
||||
```bash
|
||||
sudo systemctl restart xray
|
||||
sudo systemctl enable xray
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2. Install telemt on Server B (_EU_)
|
||||
|
||||
telemt installation is heavily covered in the [Quick Start Guide](../Quick_start/QUICK_START_GUIDE.en.md).
|
||||
By contrast to standard setups, telemt must listen strictly _locally_ (since Xray occupies the public `443` interface) and must expect `PROXYv2` packets.
|
||||
|
||||
Edit the configuration file (`config.toml`) on Server B accordingly:
|
||||
|
||||
```toml
|
||||
[server]
|
||||
port = 8443
|
||||
listen_addr_ipv4 = "127.0.0.1"
|
||||
proxy_protocol = true
|
||||
|
||||
[general.links]
|
||||
show = "*"
|
||||
public_host = "<FQDN_OR_IP_SERVER_A>"
|
||||
public_port = 443
|
||||
```
|
||||
|
||||
- Address `127.0.0.1` and `port = 8443` instructs the core proxy router to process connections unpacked locally via Xray-server.
|
||||
- `proxy_protocol = true` commands telemt to parse the injected PROXY header (from Server A's Xray local loopback) and log genuine end-user IPs.
|
||||
- Under `public_host`, place Server A's public IP address or FQDN to ensure working links are generated for Telegram users.
|
||||
|
||||
Restart `telemt`. Your server is now robust against DPI scanners, passing traffic optimally.
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
<img src="https://gist.githubusercontent.com/avbor/1f8a128e628f47249aae6e058a57610b/raw/19013276c035e91058e0a9799ab145f8e70e3ff5/scheme.svg">
|
||||
|
||||
## Концепция
|
||||
- **Сервер A** (_РФ_):\
|
||||
Точка входа, принимает трафик пользователей Telegram-прокси напрямую через **Xray** (порт `443\tcp`)\
|
||||
и отправляет его в туннель на Сервер **B**.\
|
||||
Порт для клиентов Telegram — `443\tcp`
|
||||
- **Сервер B** (_условно Нидерланды_):\
|
||||
Точка выхода, на нем работает **Xray-сервер** (принимает подключения точки входа) и **telemt**.\
|
||||
На сервере должен быть неограниченный доступ до серверов Telegram.\
|
||||
Порт для VLESS/REALITY (вход) — `443\tcp`\
|
||||
Внутренний порт telemt (куда пробрасывается трафик) — `8443\tcp`
|
||||
|
||||
Туннель работает по протоколу VLESS-XTLS-Reality (или VLESS/xhttp/reality). Оригинальный IP-адрес клиента сохраняется благодаря протоколу PROXYv2, который Xray на Сервере А добавляет через локальный loopback перед упаковкой в туннель, благодаря чему прозрачно доходит до telemt.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 1. Настройка туннеля Xray (A <-> B)
|
||||
|
||||
На обоих серверах необходимо установить **Xray-core** (рекомендуется версия 1.8.4 или новее).
|
||||
Официальный скрипт установки (выполнить на обоих серверах):
|
||||
```bash
|
||||
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
|
||||
```
|
||||
|
||||
### Генерация ключей и параметров (выполнить один раз)
|
||||
Для конфигурации потребуются уникальные ID и ключи Xray Reality. Выполните на любом сервере с установленным Xray:
|
||||
1. **UUID клиента:**
|
||||
```bash
|
||||
xray uuid
|
||||
# Сохраните вывод (например: 12345678-abcd-1234-abcd-1234567890ab) — это <XRAY_UUID>
|
||||
```
|
||||
2. **Пара ключей X25519 (Private & Public) для Reality:**
|
||||
```bash
|
||||
xray x25519
|
||||
# Сохраните Private key (<SERVER_B_PRIVATE_KEY>) и Public key (<SERVER_B_PUBLIC_KEY>)
|
||||
```
|
||||
3. **Short ID (идентификатор Reality):**
|
||||
```bash
|
||||
openssl rand -hex 16
|
||||
# Сохраните вывод (например: 0123456789abcdef0123456789abcdef) — это <SHORT_ID>
|
||||
```
|
||||
4. **Random Path (путь для xhttp):**
|
||||
```bash
|
||||
openssl rand -hex 8
|
||||
# Сохраните вывод (например, abc123def456), чтобы заменить <YOUR_RANDOM_PATH> в конфигах
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Конфигурация Сервера B (_Нидерланды_):
|
||||
|
||||
Создаем или редактируем файл `/usr/local/etc/xray/config.json`.
|
||||
Этот Xray-сервер будет слушать порт `443` и прозрачно пропускать валидный Reality трафик дальше, а "замаскированный" трафик (например, если кто-то стучится в лоб веб-браузером) пойдет на `yahoo.com`.
|
||||
|
||||
```bash
|
||||
nano /usr/local/etc/xray/config.json
|
||||
```
|
||||
|
||||
Содержимое файла:
|
||||
```json
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "error",
|
||||
"access": "none"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "vless-in",
|
||||
"port": 443,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "<XRAY_UUID>"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "xhttp",
|
||||
"security": "reality",
|
||||
"realitySettings": {
|
||||
"dest": "yahoo.com:443",
|
||||
"serverNames": [
|
||||
"yahoo.com"
|
||||
],
|
||||
"privateKey": "<SERVER_B_PRIVATE_KEY>",
|
||||
"shortIds": [
|
||||
"<SHORT_ID>"
|
||||
]
|
||||
},
|
||||
"xhttpSettings": {
|
||||
"path": "/<YOUR_RANDOM_PATH>",
|
||||
"mode": "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "tunnel-to-telemt",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"destination": "127.0.0.1:8443"
|
||||
}
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": [
|
||||
"vless-in"
|
||||
],
|
||||
"outboundTag": "tunnel-to-telemt"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Открываем порт на фаерволе (если включен):
|
||||
```bash
|
||||
sudo ufw allow 443/tcp
|
||||
```
|
||||
Перезапускаем Xray:
|
||||
```bash
|
||||
sudo systemctl restart xray
|
||||
sudo systemctl enable xray
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Конфигурация Сервера A (_РФ_):
|
||||
|
||||
Аналогично, редактируем `/usr/local/etc/xray/config.json`.
|
||||
Здесь Xray выступает публичной точкой: он принимает трафик на внешний порт `443\tcp`, пропускает через локальный loopback (порт `10444`) для добавления PROXYv2-заголовка, и упаковывает в Reality до Сервера B, прося тот доставить данные на *свой локальный* порт `127.0.0.1:8443` (именно там будет слушать telemt).
|
||||
|
||||
```bash
|
||||
nano /usr/local/etc/xray/config.json
|
||||
```
|
||||
|
||||
Содержимое файла:
|
||||
```json
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "error",
|
||||
"access": "none"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "public-in",
|
||||
"port": 443,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 10444,
|
||||
"network": "tcp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "tunnel-in",
|
||||
"port": 10444,
|
||||
"listen": "127.0.0.1",
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 8443,
|
||||
"network": "tcp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "local-injector",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"proxyProtocol": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "vless-out",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"vnext": [
|
||||
{
|
||||
"address": "<PUBLIC_IP_SERVER_B>",
|
||||
"port": 443,
|
||||
"users": [
|
||||
{
|
||||
"id": "<XRAY_UUID>",
|
||||
"encryption": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "xhttp",
|
||||
"security": "reality",
|
||||
"realitySettings": {
|
||||
"serverName": "yahoo.com",
|
||||
"publicKey": "<SERVER_B_PUBLIC_KEY>",
|
||||
"shortId": "<SHORT_ID>",
|
||||
"spiderX": "/",
|
||||
"fingerprint": "chrome"
|
||||
},
|
||||
"xhttpSettings": {
|
||||
"path": "/<YOUR_RANDOM_PATH>"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": ["public-in"],
|
||||
"outboundTag": "local-injector"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": ["tunnel-in"],
|
||||
"outboundTag": "vless-out"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
*Замените `<PUBLIC_IP_SERVER_B>` на внешний IP-адрес Сервера B.*
|
||||
|
||||
Открываем порт на фаерволе для клиентов:
|
||||
```bash
|
||||
sudo ufw allow 443/tcp
|
||||
```
|
||||
|
||||
Перезапускаем Xray:
|
||||
```bash
|
||||
sudo systemctl restart xray
|
||||
sudo systemctl enable xray
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 2. Установка и настройка telemt на Сервере B (_Нидерланды_)
|
||||
|
||||
Установка telemt описана [в основной инструкции](../Quick_start/QUICK_START_GUIDE.ru.md).
|
||||
Отличие в том, что telemt должен слушать *внутренний* порт (так как 443 занят Xray-сервером), а также ожидать `PROXY` протокол из Xray туннеля.
|
||||
|
||||
В конфиге `config.toml` прокси (на Сервере B) укажите:
|
||||
```toml
|
||||
[server]
|
||||
port = 8443
|
||||
listen_addr_ipv4 = "127.0.0.1"
|
||||
proxy_protocol = true
|
||||
|
||||
[general.links]
|
||||
show = "*"
|
||||
public_host = "<FQDN_OR_IP_SERVER_A>"
|
||||
public_port = 443
|
||||
```
|
||||
|
||||
- `port = 8443` и `listen_addr_ipv4 = "127.0.0.1"` означают, что telemt принимает подключения только изнутри (приходящие от локального Xray-процесса).
|
||||
- `proxy_protocol = true` заставляет telemt парсить PROXYv2-заголовок (который добавил Xray на Сервере A через loopback), восстанавливая IP-адрес конечного пользователя (РФ).
|
||||
- В `public_host` укажите публичный IP-адрес или домен Сервера A, чтобы ссылки на подключение генерировались корректно.
|
||||
|
||||
Перезапустите `telemt`, и клиенты смогут подключаться по выданным ссылкам.
|
||||
|
||||
+1
-1
@@ -1730,7 +1730,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_protocol_trusted_cidrs_missing_uses_trust_all_but_explicit_empty_stays_empty() {
|
||||
fn proxy_protocol_trusted_cidrs_missing_defaults_to_empty_and_explicit_empty_stays_empty() {
|
||||
let cfg_missing: ProxyConfig = toml::from_str(
|
||||
r#"
|
||||
[server]
|
||||
|
||||
+15
-7
@@ -8,8 +8,6 @@ use hmac::{Hmac, Mac};
|
||||
#[cfg(test)]
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
#[cfg(test)]
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::net::SocketAddr;
|
||||
use std::net::{IpAddr, Ipv6Addr};
|
||||
@@ -254,6 +252,19 @@ fn budget_for_validation(total_users: usize, overload: bool, has_hint: bool) ->
|
||||
total_users.min(cap.max(1))
|
||||
}
|
||||
|
||||
// Fold the peer address into a stable scan offset seed without invoking any
|
||||
// cryptographic or keyed hashing. This only needs to fan peers out across the
|
||||
// overload validation ring so repeated partial scans do not start at the same slot.
|
||||
fn candidate_scan_peer_seed(peer_ip: IpAddr) -> usize {
|
||||
match peer_ip {
|
||||
IpAddr::V4(ip) => u32::from_be_bytes(ip.octets()) as usize,
|
||||
IpAddr::V6(ip) => {
|
||||
let raw = u128::from_be_bytes(ip.octets());
|
||||
((raw >> 64) as u64 ^ raw as u64) as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(
|
||||
@@ -263,17 +274,14 @@ fn candidate_scan_start_offset_in(
|
||||
candidate_budget: usize,
|
||||
) -> usize {
|
||||
if total_users == 0 || candidate_budget >= total_users {
|
||||
return 0;
|
||||
return total_users.saturating_sub(total_users);
|
||||
}
|
||||
|
||||
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
|
||||
candidate_scan_peer_seed(peer_ip).wrapping_add(seq as usize) % total_users
|
||||
}
|
||||
|
||||
fn parse_tls_auth_material(
|
||||
|
||||
@@ -1339,11 +1339,8 @@ async fn mtproto_overload_full_scans_small_runtime_snapshot_to_preserve_cold_use
|
||||
}
|
||||
|
||||
let replay_checker = ReplayChecker::new(128, Duration::from_secs(60));
|
||||
let handshake = make_valid_mtproto_handshake(
|
||||
"00000000000000000000000000000020",
|
||||
ProtoTag::Secure,
|
||||
2,
|
||||
);
|
||||
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(
|
||||
|
||||
+4402
-242
File diff suppressed because it is too large
Load Diff
+56
-55
@@ -24,7 +24,7 @@ from urllib.request import Request, urlopen
|
||||
# Exceptions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TememtAPIError(Exception):
|
||||
class TelemtAPIError(Exception):
|
||||
"""Raised when the API returns an error envelope or a transport error."""
|
||||
|
||||
def __init__(self, message: str, code: str | None = None,
|
||||
@@ -35,7 +35,7 @@ class TememtAPIError(Exception):
|
||||
self.request_id = request_id
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (f"TememtAPIError(message={str(self)!r}, code={self.code!r}, "
|
||||
return (f"TelemtAPIError(message={str(self)!r}, code={self.code!r}, "
|
||||
f"http_status={self.http_status}, request_id={self.request_id})")
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class APIResponse:
|
||||
# Main client
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TememtAPI:
|
||||
class TelemtAPI:
|
||||
"""
|
||||
HTTP client for the Telemt Control API.
|
||||
|
||||
@@ -75,10 +75,10 @@ class TememtAPI:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str = "http://127.0.0.1:9091",
|
||||
auth_header: str | None = None,
|
||||
timeout: int = 10,
|
||||
self,
|
||||
base_url: str = "http://127.0.0.1:9091",
|
||||
auth_header: str | None = None,
|
||||
timeout: int = 10,
|
||||
) -> None:
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.auth_header = auth_header
|
||||
@@ -98,12 +98,12 @@ class TememtAPI:
|
||||
return h
|
||||
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
body: dict | None = None,
|
||||
if_match: str | None = None,
|
||||
query: dict | None = None,
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
body: dict | None = None,
|
||||
if_match: str | None = None,
|
||||
query: dict | None = None,
|
||||
) -> APIResponse:
|
||||
url = self.base_url + path
|
||||
if query:
|
||||
@@ -133,22 +133,22 @@ class TememtAPI:
|
||||
try:
|
||||
payload = json.loads(raw)
|
||||
except Exception:
|
||||
raise TememtAPIError(
|
||||
raise TelemtAPIError(
|
||||
str(exc), http_status=exc.code
|
||||
) from exc
|
||||
err = payload.get("error", {})
|
||||
raise TememtAPIError(
|
||||
raise TelemtAPIError(
|
||||
err.get("message", str(exc)),
|
||||
code=err.get("code"),
|
||||
http_status=exc.code,
|
||||
request_id=payload.get("request_id"),
|
||||
) from exc
|
||||
except URLError as exc:
|
||||
raise TememtAPIError(str(exc)) from exc
|
||||
raise TelemtAPIError(str(exc)) from exc
|
||||
|
||||
if not payload.get("ok"):
|
||||
err = payload.get("error", {})
|
||||
raise TememtAPIError(
|
||||
raise TelemtAPIError(
|
||||
err.get("message", "unknown error"),
|
||||
code=err.get("code"),
|
||||
request_id=payload.get("request_id"),
|
||||
@@ -298,16 +298,16 @@ class TememtAPI:
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def create_user(
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
secret: str | None = None,
|
||||
user_ad_tag: str | None = None,
|
||||
max_tcp_conns: int | None = None,
|
||||
expiration_rfc3339: str | None = None,
|
||||
data_quota_bytes: int | None = None,
|
||||
max_unique_ips: int | None = None,
|
||||
if_match: str | None = None,
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
secret: str | None = None,
|
||||
user_ad_tag: str | None = None,
|
||||
max_tcp_conns: int | None = None,
|
||||
expiration_rfc3339: str | None = None,
|
||||
data_quota_bytes: int | None = None,
|
||||
max_unique_ips: int | None = None,
|
||||
if_match: str | None = None,
|
||||
) -> APIResponse:
|
||||
"""POST /v1/users — create a new user.
|
||||
|
||||
@@ -340,16 +340,16 @@ class TememtAPI:
|
||||
return self._post("/v1/users", body=body, if_match=if_match)
|
||||
|
||||
def patch_user(
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
secret: str | None = None,
|
||||
user_ad_tag: str | None = None,
|
||||
max_tcp_conns: int | None = None,
|
||||
expiration_rfc3339: str | None = None,
|
||||
data_quota_bytes: int | None = None,
|
||||
max_unique_ips: int | None = None,
|
||||
if_match: str | None = None,
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
secret: str | None = None,
|
||||
user_ad_tag: str | None = None,
|
||||
max_tcp_conns: int | None = None,
|
||||
expiration_rfc3339: str | None = None,
|
||||
data_quota_bytes: int | None = None,
|
||||
max_unique_ips: int | None = None,
|
||||
if_match: str | None = None,
|
||||
) -> APIResponse:
|
||||
"""PATCH /v1/users/{username} — partial update; only provided fields change.
|
||||
|
||||
@@ -385,10 +385,10 @@ class TememtAPI:
|
||||
if_match=if_match)
|
||||
|
||||
def delete_user(
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
if_match: str | None = None,
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
if_match: str | None = None,
|
||||
) -> APIResponse:
|
||||
"""DELETE /v1/users/{username} — remove user; blocks deletion of last user.
|
||||
|
||||
@@ -403,11 +403,11 @@ class TememtAPI:
|
||||
# in the route matcher (documented limitation). The method is provided
|
||||
# for completeness and future compatibility.
|
||||
def rotate_secret(
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
secret: str | None = None,
|
||||
if_match: str | None = None,
|
||||
self,
|
||||
username: str,
|
||||
*,
|
||||
secret: str | None = None,
|
||||
if_match: str | None = None,
|
||||
) -> APIResponse:
|
||||
"""POST /v1/users/{username}/rotate-secret — rotate user secret.
|
||||
|
||||
@@ -533,12 +533,12 @@ EXAMPLES
|
||||
help="Username for user commands")
|
||||
|
||||
# user create/patch fields
|
||||
p.add_argument("--secret", default=None)
|
||||
p.add_argument("--ad-tag", dest="ad_tag", default=None)
|
||||
p.add_argument("--secret", default=None)
|
||||
p.add_argument("--ad-tag", dest="ad_tag", default=None)
|
||||
p.add_argument("--max-conns", dest="max_conns", type=int, default=None)
|
||||
p.add_argument("--expires", default=None)
|
||||
p.add_argument("--quota", type=int, default=None)
|
||||
p.add_argument("--max-ips", dest="max_ips", type=int, default=None)
|
||||
p.add_argument("--expires", default=None)
|
||||
p.add_argument("--quota", type=int, default=None)
|
||||
p.add_argument("--max-ips", dest="max_ips", type=int, default=None)
|
||||
|
||||
# events
|
||||
p.add_argument("--limit", type=int, default=None,
|
||||
@@ -564,10 +564,10 @@ if __name__ == "__main__":
|
||||
sys.exit(0)
|
||||
|
||||
if cmd == "gen-secret":
|
||||
print(TememtAPI.generate_secret())
|
||||
print(TelemtAPI.generate_secret())
|
||||
sys.exit(0)
|
||||
|
||||
api = TememtAPI(args.url, auth_header=args.auth, timeout=args.timeout)
|
||||
api = TelemtAPI(args.url, auth_header=args.auth, timeout=args.timeout)
|
||||
|
||||
try:
|
||||
# -- read endpoints --------------------------------------------------
|
||||
@@ -690,7 +690,8 @@ if __name__ == "__main__":
|
||||
parser.error("patch command requires <username>")
|
||||
if not any([args.secret, args.ad_tag, args.max_conns,
|
||||
args.expires, args.quota, args.max_ips]):
|
||||
parser.error("patch requires at least one field (--secret, --max-conns, --expires, --quota, --max-ips, --ad-tag)")
|
||||
parser.error(
|
||||
"patch requires at least one field (--secret, --max-conns, --expires, --quota, --max-ips, --ad-tag)")
|
||||
_print(api.patch_user(
|
||||
args.arg,
|
||||
secret=args.secret,
|
||||
@@ -721,7 +722,7 @@ if __name__ == "__main__":
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
except TememtAPIError as exc:
|
||||
except TelemtAPIError as exc:
|
||||
print(f"API error [{exc.http_status}] {exc.code}: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
|
||||
Reference in New Issue
Block a user