16 Commits
3.0.3 ... main

Author SHA1 Message Date
Alexey
301f829c3c Update LICENSING.md 2026-02-19 03:00:47 +03:00
Alexey
76a02610d8 Create LICENSING.md
Drafting licensing...
2026-02-19 03:00:04 +03:00
Alexey
76bf5337e8 Update CONTRIBUTING.md 2026-02-19 02:49:38 +03:00
Alexey
e76b388a05 Create CONTRIBUTING.md 2026-02-19 02:49:08 +03:00
Alexey
f37e6cbe29 Merge pull request #155 from unuunn/feat/scoped-routing
feat: implement selective routing for "scope_*" users
2026-02-19 02:19:42 +03:00
unuunn
c7464d53e1 feat: implement selective routing for "scope_*" users
- Users with "scope_{name}" prefix are routed to upstreams where {name}
  is present in the "scopes" property (comma-separated).
- Strict separation: Scoped upstreams are excluded from general routing, and vice versa.
- Constraint: SOCKS upstreams and DIRECT(`use_middle_proxy =
false`) mode only.

Example:
  User "scope_hello" matches an upstream with `scopes = "world,hello"`
2026-02-18 23:29:08 +03:00
Alexey
03a6493147 Merge pull request #153 from vladon/fix/release-changes-package-version
release changes package version
2026-02-18 23:23:04 +03:00
Vladislav Yaroslavlev
36ef2f722d release changes package version 2026-02-18 22:46:45 +03:00
Alexey
b9fda9e2c2 Merge pull request #151 from vladon/fix-ci2
fix(ci) 2nd try
2026-02-18 22:34:30 +03:00
Vladislav Yaroslavlev
c5b590062c fix(ci): replace deprecated actions-rs/cargo with direct cross commands
The actions-rs organization has been archived and is no longer available.
Replace the deprecated action with direct cross installation and build commands.
2026-02-18 22:10:17 +03:00
Alexey
c0357b2890 Merge pull request #149 from vladon/fix/ci-deprecated-actions-rs
fix(ci): replace deprecated actions-rs/cargo with direct cross commands
2026-02-18 22:02:16 +03:00
Vladislav Yaroslavlev
4f7f7d6880 fix(ci): replace deprecated actions-rs/cargo with direct cross commands
The actions-rs organization has been archived and is no longer available.
Replace the deprecated action with direct cross installation and build commands.
2026-02-18 21:49:42 +03:00
Alexey
efba10f839 Update README.md 2026-02-18 21:34:04 +03:00
Alexey
6ba12f35d0 Update README.md 2026-02-18 21:31:58 +03:00
Alexey
6a57c23700 Update README.md 2026-02-18 20:56:03 +03:00
Alexey
94b85afbc5 Update Cargo.toml 2026-02-18 20:25:17 +03:00
9 changed files with 139 additions and 75 deletions

View File

@@ -56,12 +56,11 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.target }}-cargo- ${{ runner.os }}-${{ matrix.target }}-cargo-
- name: Install cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Build Release - name: Build Release
uses: actions-rs/cargo@ae10961054e4aa8bff448f48a500763b90d5c550 # v1.0.1 run: cross build --release --target ${{ matrix.target }}
with:
use-cross: true
command: build
args: --release --target ${{ matrix.target }}
- name: Package binary - name: Package binary
run: | run: |
@@ -85,13 +84,42 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Download all artifacts - name: Download all artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
path: artifacts 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 - name: Create Release
uses: softprops/action-gh-release@c95fe1489396fe360a41fb53f90de6ddce8c4c8a # v2.2.1 uses: softprops/action-gh-release@v2
with: with:
files: artifacts/**/* files: artifacts/**/*
generate_release_notes: true generate_release_notes: true

5
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,5 @@
## Pull Requests - Rules
- ONLY signed and verified commits
- ONLY from your name
- DO NOT commit with `codex` or `claude` as author/commiter
- PREFER `flow` branch for development, not `main`

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "telemt" name = "telemt"
version = "3.0.3" version = "3.0.4"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

17
LICENSING.md Normal file
View File

@@ -0,0 +1,17 @@
# LICENSING
## Licenses for Versions
| Version | License |
|---------|---------------|
| 1.0 | NO LICNESE |
| 1.1 | NO LICENSE |
| 1.2 | NO LICENSE |
| 2.0 | NO LICENSE |
| 3.0 | TELEMT UL 1 |
### License Types
- **NO LICENSE** = ***ALL RIGHT RESERVED***
- **TELEMT UL1** - work in progress license for source code of `telemt`, which encourages:
- fair use,
- contributions,
- distribution,
- but prohibits NOT mentioning the authors

View File

@@ -10,74 +10,40 @@
### 🇷🇺 RU ### 🇷🇺 RU
15 февраля мы опубликовали `telemt 3` с поддержкой Middle-End Proxy, а значит: 18 февраля мы опубликовали `telemt 3.0.3`, он имеет:
- с функциональными медиа, в том числе с CDN/DC=203 - улучшенный механизм Middle-End Health Check
- с Ad-tag — показывайте спонсорский канал и собирайте статистику через официального бота - высокоскоростное восстановление инициализации Middle-End
- с новым подходом к безопасности и асинхронности - меньше задержек на hot-path
- с высокоточной диагностикой криптографии через `ME_DIAG` - более корректную работу в 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 Если у вас есть компетенции в асинхронных сетевых приложениях, анализе трафика, реверс-инжиниринге или сетевых расследованиях - мы открыты к идеям и pull requests!
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.
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
### 🇬🇧 EN ### 🇬🇧 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 - improved Middle-End Health Check method
- Ad-tag support promote a sponsored channel and collect statistics via Telegram bot - high-speed recovery of Middle-End init
- new approach to security and asynchronicity - reduced latency on the hot path
- high-precision cryptography diagnostics via `ME_DIAG` - 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: [Release is available here](https://github.com/telemt/telemt/releases/tag/3.0.3)
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
````
If the conditions from step 1 are not satisfied: If you have expertise in asynchronous network applications, traffic analysis, reverse engineering, or network forensics - we welcome ideas and pull requests!
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.
</td> </td>
</tr> </tr>
@@ -88,6 +54,8 @@ If you have expertise in asynchronous network applications, traffic analysis, re
⚓ 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 # GOTO
- [Features](#features) - [Features](#features)
- [Quick Start Guide](#quick-start-guide) - [Quick Start Guide](#quick-start-guide)

View File

@@ -208,6 +208,8 @@ impl ProxyConfig {
upstream_type: UpstreamType::Direct { interface: None }, upstream_type: UpstreamType::Direct { interface: None },
weight: 1, weight: 1,
enabled: true, enabled: true,
scopes: String::new(),
selected_scope: String::new(),
}); });
} }

View File

@@ -403,6 +403,10 @@ pub struct UpstreamConfig {
pub weight: u16, pub weight: u16,
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub enabled: bool, pub enabled: bool,
#[serde(default)]
pub scopes: String,
#[serde(skip)]
pub selected_scope: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@@ -45,7 +45,7 @@ where
); );
let tg_stream = upstream_manager 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?; .await?;
debug!(peer = %success.peer, dc_addr = %dc_addr, "Connected, performing TG handshake"); debug!(peer = %success.peer, dc_addr = %dc_addr, "Connected, performing TG handshake");

View File

@@ -167,20 +167,44 @@ impl UpstreamManager {
} }
/// Select upstream using latency-weighted random selection. /// 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; let upstreams = self.upstreams.read().await;
if upstreams.is_empty() { if upstreams.is_empty() {
return None; return None;
} }
// Scope filter:
let healthy: Vec<usize> = upstreams.iter() // 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() .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) .map(|(i, _)| i)
.collect(); .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() { 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 { if healthy.len() == 1 {
@@ -222,15 +246,20 @@ impl UpstreamManager {
} }
/// Connect to target through a selected upstream. /// Connect to target through a selected upstream.
pub async fn connect(&self, target: SocketAddr, dc_idx: Option<i16>) -> Result<TcpStream> { pub async fn connect(&self, target: SocketAddr, dc_idx: Option<i16>, scope: Option<&str>) -> Result<TcpStream> {
let idx = self.select_upstream(dc_idx).await let idx = self.select_upstream(dc_idx, scope).await
.ok_or_else(|| ProxyError::Config("No upstreams available".to_string()))?; .ok_or_else(|| ProxyError::Config("No upstreams available".to_string()))?;
let upstream = { let mut upstream = {
let guard = self.upstreams.read().await; let guard = self.upstreams.read().await;
guard[idx].config.clone() guard[idx].config.clone()
}; };
// Set scope for configuration copy
if let Some(s) = scope {
upstream.selected_scope = s.to_string();
}
let start = Instant::now(); let start = Instant::now();
match self.connect_via_upstream(&upstream, target).await { match self.connect_via_upstream(&upstream, target).await {
@@ -313,8 +342,12 @@ impl UpstreamManager {
if let Some(e) = stream.take_error()? { if let Some(e) = stream.take_error()? {
return Err(ProxyError::Io(e)); 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) Ok(stream)
}, },
UpstreamType::Socks5 { address, interface, username, password } => { UpstreamType::Socks5 { address, interface, username, password } => {
@@ -341,7 +374,14 @@ impl UpstreamManager {
return Err(ProxyError::Io(e)); 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) Ok(stream)
}, },
} }