Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f37e6cbe29 | ||
|
|
c7464d53e1 | ||
|
|
03a6493147 | ||
|
|
36ef2f722d | ||
|
|
b9fda9e2c2 | ||
|
|
c5b590062c | ||
|
|
c0357b2890 | ||
|
|
4f7f7d6880 | ||
|
|
efba10f839 | ||
|
|
6ba12f35d0 | ||
|
|
6a57c23700 | ||
|
|
94b85afbc5 |
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
@@ -56,12 +56,11 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-cargo-
|
||||
|
||||
- name: Install cross
|
||||
run: cargo install cross --git https://github.com/cross-rs/cross
|
||||
|
||||
- name: Build Release
|
||||
uses: actions-rs/cargo@ae10961054e4aa8bff448f48a500763b90d5c550 # v1.0.1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target ${{ matrix.target }}
|
||||
run: cross build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Package binary
|
||||
run: |
|
||||
@@ -85,13 +84,42 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Update version in Cargo.toml and Cargo.lock
|
||||
run: |
|
||||
# Extract version from tag (remove 'v' prefix if present)
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
VERSION="${VERSION#v}"
|
||||
|
||||
# Install cargo-edit for version bumping
|
||||
cargo install cargo-edit
|
||||
|
||||
# Update Cargo.toml version
|
||||
cargo set-version "$VERSION"
|
||||
|
||||
# Configure git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Commit and push changes
|
||||
git add Cargo.toml Cargo.lock
|
||||
git commit -m "chore: bump version to $VERSION" || echo "No changes to commit"
|
||||
git push origin HEAD:main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@c95fe1489396fe360a41fb53f90de6ddce8c4c8a # v2.2.1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: artifacts/**/*
|
||||
generate_release_notes: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "telemt"
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
82
README.md
82
README.md
@@ -10,74 +10,40 @@
|
||||
|
||||
### 🇷🇺 RU
|
||||
|
||||
15 февраля мы опубликовали `telemt 3` с поддержкой Middle-End Proxy, а значит:
|
||||
18 февраля мы опубликовали `telemt 3.0.3`, он имеет:
|
||||
|
||||
- с функциональными медиа, в том числе с CDN/DC=203
|
||||
- с Ad-tag — показывайте спонсорский канал и собирайте статистику через официального бота
|
||||
- с новым подходом к безопасности и асинхронности
|
||||
- с высокоточной диагностикой криптографии через `ME_DIAG`
|
||||
- улучшенный механизм Middle-End Health Check
|
||||
- высокоскоростное восстановление инициализации Middle-End
|
||||
- меньше задержек на hot-path
|
||||
- более корректную работу в Dualstack, а именно - IPv6 Middle-End
|
||||
- аккуратное переподключение клиента без дрифта сессий между Middle-End
|
||||
- автоматическая деградация на Direct-DC при массовой (>2 ME-DC-групп) недоступности Middle-End
|
||||
- автодетект IP за NAT, при возможности - будет выполнен хендшейк с ME, при неудаче - автодеградация
|
||||
- единственный известный специальный DC=203 уже добавлен в код: медиа загружаются с CDN в Direct-DC режиме
|
||||
|
||||
Для использования нужно:
|
||||
[Здесь вы можете найти релиз](https://github.com/telemt/telemt/releases/tag/3.0.3)
|
||||
|
||||
1. Версия `telemt` ≥3.0.0
|
||||
2. Выполнение любого из наборов условий:
|
||||
- публичный IP для исходящих соединений установлен на интерфейса инстанса с `telemt`
|
||||
- ЛИБО
|
||||
- вы используете NAT 1:1 + включили STUN-пробинг
|
||||
3. В конфиге, в секции `[general]` указать:
|
||||
```toml
|
||||
use_middle_proxy = true
|
||||
```
|
||||
|
||||
Если условия из пункта 1 не выполняются:
|
||||
1. Выключите ME-режим:
|
||||
- установите `use_middle_proxy = false`
|
||||
- ЛИБО
|
||||
- Middle-End Proxy будет выключен автоматически по таймауту, но это займёт больше времени при запуске
|
||||
2. В конфиге, добавьте в конец:
|
||||
```toml
|
||||
[dc_overrides]
|
||||
"203" = "91.105.192.100:443"
|
||||
```
|
||||
|
||||
Если у вас есть компетенции в асинхронных сетевых приложениях, анализе трафика, реверс-инжиниринге или сетевых расследованиях — мы открыты к идеям и pull requests.
|
||||
Если у вас есть компетенции в асинхронных сетевых приложениях, анализе трафика, реверс-инжиниринге или сетевых расследованиях - мы открыты к идеям и pull requests!
|
||||
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
|
||||
### 🇬🇧 EN
|
||||
|
||||
On February 15, we released `telemt 3` with support for Middle-End Proxy, which means:
|
||||
On February 18, we released `telemt 3.0.3`. This version introduces:
|
||||
|
||||
- functional media, including CDN/DC=203
|
||||
- Ad-tag support – promote a sponsored channel and collect statistics via Telegram bot
|
||||
- new approach to security and asynchronicity
|
||||
- high-precision cryptography diagnostics via `ME_DIAG`
|
||||
- improved Middle-End Health Check method
|
||||
- high-speed recovery of Middle-End init
|
||||
- reduced latency on the hot path
|
||||
- correct Dualstack support: proper handling of IPv6 Middle-End
|
||||
- *clean* client reconnection without session "drift" between Middle-End
|
||||
- automatic degradation to Direct-DC mode in case of large-scale (>2 ME-DC groups) Middle-End unavailability
|
||||
- automatic public IP detection behind NAT; first - Middle-End handshake is performed, otherwise automatic degradation is applied
|
||||
- known special DC=203 is now handled natively: media is delivered from the CDN via Direct-DC mode
|
||||
|
||||
To use this feature, the following requirements must be met:
|
||||
1. `telemt` version ≥ 3.0.0
|
||||
2. One of the following conditions satisfied:
|
||||
- the instance running `telemt` has a public IP address assigned to its network interface for outbound connections
|
||||
- OR
|
||||
- you are using 1:1 NAT and have STUN probing enabled
|
||||
3. In the config file, under the `[general]` section, specify:
|
||||
```toml
|
||||
use_middle_proxy = true
|
||||
````
|
||||
[Release is available here](https://github.com/telemt/telemt/releases/tag/3.0.3)
|
||||
|
||||
If the conditions from step 1 are not satisfied:
|
||||
1. Disable Middle-End mode:
|
||||
- set `use_middle_proxy = false`
|
||||
- OR
|
||||
- Middle-End Proxy will be disabled automatically after a timeout, but this will increase startup time
|
||||
|
||||
2. In the config file, add the following at the end:
|
||||
```toml
|
||||
[dc_overrides]
|
||||
"203" = "91.105.192.100:443"
|
||||
```
|
||||
|
||||
If you have expertise in asynchronous network applications, traffic analysis, reverse engineering, or network forensics — we welcome ideas, suggestions, and pull requests.
|
||||
If you have expertise in asynchronous network applications, traffic analysis, reverse engineering, or network forensics - we welcome ideas and pull requests!
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
@@ -86,7 +52,9 @@ If you have expertise in asynchronous network applications, traffic analysis, re
|
||||
# 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
|
||||
|
||||
# GOTO
|
||||
- [Features](#features)
|
||||
|
||||
@@ -208,6 +208,8 @@ impl ProxyConfig {
|
||||
upstream_type: UpstreamType::Direct { interface: None },
|
||||
weight: 1,
|
||||
enabled: true,
|
||||
scopes: String::new(),
|
||||
selected_scope: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -403,6 +403,10 @@ pub struct UpstreamConfig {
|
||||
pub weight: u16,
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
#[serde(default)]
|
||||
pub scopes: String,
|
||||
#[serde(skip)]
|
||||
pub selected_scope: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -45,7 +45,7 @@ where
|
||||
);
|
||||
|
||||
let tg_stream = upstream_manager
|
||||
.connect(dc_addr, Some(success.dc_idx))
|
||||
.connect(dc_addr, Some(success.dc_idx), user.strip_prefix("scope_").filter(|s| !s.is_empty()))
|
||||
.await?;
|
||||
|
||||
debug!(peer = %success.peer, dc_addr = %dc_addr, "Connected, performing TG handshake");
|
||||
|
||||
@@ -167,20 +167,44 @@ impl UpstreamManager {
|
||||
}
|
||||
|
||||
/// Select upstream using latency-weighted random selection.
|
||||
async fn select_upstream(&self, dc_idx: Option<i16>) -> Option<usize> {
|
||||
async fn select_upstream(&self, dc_idx: Option<i16>, scope: Option<&str>) -> Option<usize> {
|
||||
let upstreams = self.upstreams.read().await;
|
||||
if upstreams.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let healthy: Vec<usize> = upstreams.iter()
|
||||
// Scope filter:
|
||||
// If scope is set: only scoped and matched items
|
||||
// If scope is not set: only unscoped items
|
||||
let filtered_upstreams : Vec<usize> = upstreams.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, u)| u.healthy)
|
||||
.filter(|(_, u)| {
|
||||
scope.map_or(
|
||||
u.config.scopes.is_empty(),
|
||||
|req_scope| {
|
||||
u.config.scopes
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.any(|s| s == req_scope)
|
||||
}
|
||||
)
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
|
||||
// Healthy filter
|
||||
let healthy: Vec<usize> = filtered_upstreams.iter()
|
||||
.filter(|&&i| upstreams[i].healthy)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
if filtered_upstreams.is_empty() {
|
||||
warn!(scope = scope, "No upstreams available! Using first (direct?)");
|
||||
return None;
|
||||
}
|
||||
|
||||
if healthy.is_empty() {
|
||||
return Some(rand::rng().gen_range(0..upstreams.len()));
|
||||
warn!(scope = scope, "No healthy upstreams available! Using random.");
|
||||
return Some(filtered_upstreams[rand::rng().gen_range(0..filtered_upstreams.len())]);
|
||||
}
|
||||
|
||||
if healthy.len() == 1 {
|
||||
@@ -222,15 +246,20 @@ impl UpstreamManager {
|
||||
}
|
||||
|
||||
/// Connect to target through a selected upstream.
|
||||
pub async fn connect(&self, target: SocketAddr, dc_idx: Option<i16>) -> Result<TcpStream> {
|
||||
let idx = self.select_upstream(dc_idx).await
|
||||
pub async fn connect(&self, target: SocketAddr, dc_idx: Option<i16>, scope: Option<&str>) -> Result<TcpStream> {
|
||||
let idx = self.select_upstream(dc_idx, scope).await
|
||||
.ok_or_else(|| ProxyError::Config("No upstreams available".to_string()))?;
|
||||
|
||||
let upstream = {
|
||||
let mut upstream = {
|
||||
let guard = self.upstreams.read().await;
|
||||
guard[idx].config.clone()
|
||||
};
|
||||
|
||||
// Set scope for configuration copy
|
||||
if let Some(s) = scope {
|
||||
upstream.selected_scope = s.to_string();
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
match self.connect_via_upstream(&upstream, target).await {
|
||||
@@ -313,8 +342,12 @@ impl UpstreamManager {
|
||||
if let Some(e) = stream.take_error()? {
|
||||
return Err(ProxyError::Io(e));
|
||||
}
|
||||
// replace socks user_id with config.selected_scope, if set
|
||||
let scope: Option<&str> = Some(config.selected_scope.as_str())
|
||||
.filter(|s| !s.is_empty());
|
||||
let _user_id: Option<&str> = scope.or(user_id.as_deref());
|
||||
|
||||
connect_socks4(&mut stream, target, user_id.as_deref()).await?;
|
||||
connect_socks4(&mut stream, target, _user_id).await?;
|
||||
Ok(stream)
|
||||
},
|
||||
UpstreamType::Socks5 { address, interface, username, password } => {
|
||||
@@ -341,7 +374,14 @@ impl UpstreamManager {
|
||||
return Err(ProxyError::Io(e));
|
||||
}
|
||||
|
||||
connect_socks5(&mut stream, target, username.as_deref(), password.as_deref()).await?;
|
||||
debug!(config = ?config, "Socks5 connection");
|
||||
// replace socks user:pass with config.selected_scope, if set
|
||||
let scope: Option<&str> = Some(config.selected_scope.as_str())
|
||||
.filter(|s| !s.is_empty());
|
||||
let _username: Option<&str> = scope.or(username.as_deref());
|
||||
let _password: Option<&str> = scope.or(password.as_deref());
|
||||
|
||||
connect_socks5(&mut stream, target, _username, _password).await?;
|
||||
Ok(stream)
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user