mirror of https://github.com/by-sonic/tglock.git
Split into lib + CLI/GUI, add cross-platform support
Refactor project structure: extract library crate, separate CLI and GUI binaries. GUI is now an optional feature (Windows-only). CLI works on Linux, macOS, and Windows with clap for argument parsing. Add cross-platform DNS management (resolvectl / resolv.conf) and root check for non-Windows systems. Update README with CLI usage docs.
This commit is contained in:
parent
c40ad94e11
commit
b8987b666a
31
Cargo.toml
31
Cargo.toml
|
|
@ -3,24 +3,37 @@ name = "tg_unblock"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "tg_unblock"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tg_unblock"
|
||||||
|
path = "src/bin/cli.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tg_unblock_gui"
|
||||||
|
path = "src/bin/gui.rs"
|
||||||
|
required-features = ["gui"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
gui = ["dep:eframe", "dep:egui", "dep:open"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = "0.31"
|
|
||||||
egui = "0.31"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
open = "5"
|
|
||||||
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
|
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
aes = "0.8"
|
aes = "0.8"
|
||||||
ctr = "0.9"
|
ctr = "0.9"
|
||||||
cipher = "0.4"
|
cipher = "0.4"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
|
# GUI (optional)
|
||||||
|
eframe = { version = "0.31", optional = true }
|
||||||
|
egui = { version = "0.31", optional = true }
|
||||||
|
open = { version = "5", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["winuser"] }
|
winapi = { version = "0.3", features = ["winuser"] }
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tg_unblock"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
|
||||||
100
README.md
100
README.md
|
|
@ -2,14 +2,14 @@
|
||||||
<h1 align="center">TG Unblock</h1>
|
<h1 align="center">TG Unblock</h1>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b>Обход блокировки Telegram через WebSocket-туннель</b><br>
|
<b>Обход блокировки Telegram через WebSocket-туннель</b><br>
|
||||||
Без VPN. Без серверов. Без абонентки. Один клик.
|
Без VPN. Без серверов. Без абонентки. CLI + GUI.
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/by-sonic/tglock/releases"><img src="https://img.shields.io/github/v/release/by-sonic/tglock?style=for-the-badge&color=blue" alt="Release"></a>
|
<a href="https://github.com/by-sonic/tglock/releases"><img src="https://img.shields.io/github/v/release/by-sonic/tglock?style=for-the-badge&color=blue" alt="Release"></a>
|
||||||
<a href="https://github.com/by-sonic/tglock/blob/main/LICENSE"><img src="https://img.shields.io/github/license/by-sonic/tglock?style=for-the-badge" alt="License"></a>
|
<a href="https://github.com/by-sonic/tglock/blob/main/LICENSE"><img src="https://img.shields.io/github/license/by-sonic/tglock?style=for-the-badge" alt="License"></a>
|
||||||
<a href="https://github.com/by-sonic/tglock/stargazers"><img src="https://img.shields.io/github/stars/by-sonic/tglock?style=for-the-badge&color=yellow" alt="Stars"></a>
|
<a href="https://github.com/by-sonic/tglock/stargazers"><img src="https://img.shields.io/github/stars/by-sonic/tglock?style=for-the-badge&color=yellow" alt="Stars"></a>
|
||||||
<img src="https://img.shields.io/badge/rust-1.70%2B-orange?style=for-the-badge&logo=rust" alt="Rust">
|
<img src="https://img.shields.io/badge/rust-1.70%2B-orange?style=for-the-badge&logo=rust" alt="Rust">
|
||||||
<img src="https://img.shields.io/badge/platform-Windows-0078D6?style=for-the-badge&logo=windows" alt="Windows">
|
<img src="https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-0078D6?style=for-the-badge" alt="Platform">
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -17,7 +17,11 @@
|
||||||
|
|
||||||
## Что это?
|
## Что это?
|
||||||
|
|
||||||
**TG Unblock** — десктопное приложение на Rust, которое обходит блокировку Telegram через локальный WebSocket-прокси. Провайдер видит обычный HTTPS к `web.telegram.org`, а не MTProto — DPI не может обнаружить и заблокировать трафик.
|
**TG Unblock** — кроссплатформенное приложение на Rust, которое обходит блокировку Telegram через локальный WebSocket-прокси. Провайдер видит обычный HTTPS к `web.telegram.org`, а не MTProto — DPI не может обнаружить и заблокировать трафик.
|
||||||
|
|
||||||
|
Доступно в двух вариантах:
|
||||||
|
- **CLI** — кроссплатформенный (Linux, macOS, Windows), работает в терминале
|
||||||
|
- **GUI** — графический интерфейс для Windows
|
||||||
|
|
||||||
### Почему не GoodbyeDPI / Zapret?
|
### Почему не GoodbyeDPI / Zapret?
|
||||||
|
|
||||||
|
|
@ -28,7 +32,7 @@
|
||||||
| IP-шейпинг обходит? | Нет | Нет | **Да** |
|
| IP-шейпинг обходит? | Нет | Нет | **Да** |
|
||||||
| Скорость | Зависит от DPI | Зависит от DPI | **Полная** |
|
| Скорость | Зависит от DPI | Зависит от DPI | **Полная** |
|
||||||
| Переподключения | Возможны | Возможны | **Нет** |
|
| Переподключения | Возможны | Возможны | **Нет** |
|
||||||
| Настройка | Много параметров | Стратегии | **Один клик** |
|
| Настройка | Много параметров | Стратегии | **Один клик / одна команда** |
|
||||||
|
|
||||||
## Скачать
|
## Скачать
|
||||||
|
|
||||||
|
|
@ -39,28 +43,62 @@
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/by-sonic/tglock.git
|
git clone https://github.com/by-sonic/tglock.git
|
||||||
cd tglock
|
cd tglock
|
||||||
cargo build --release
|
|
||||||
|
# CLI (Linux / macOS / Windows):
|
||||||
|
cargo build --release --bin tg_unblock
|
||||||
|
|
||||||
|
# GUI (Windows):
|
||||||
|
cargo build --release --bin tg_unblock_gui --features gui
|
||||||
```
|
```
|
||||||
|
|
||||||
Готовый `.exe` будет в `target/release/tg_unblock.exe`.
|
Готовый бинарник будет в `target/release/`.
|
||||||
|
|
||||||
## Как пользоваться
|
## Как пользоваться
|
||||||
|
|
||||||
1. Запустите `tg_unblock.exe`
|
### CLI (Linux / macOS / Windows)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запустить прокси на порту по умолчанию (1080):
|
||||||
|
tg_unblock
|
||||||
|
|
||||||
|
# Указать порт:
|
||||||
|
tg_unblock --port 9050
|
||||||
|
|
||||||
|
# Со сменой DNS на Cloudflare (нужен root/admin):
|
||||||
|
sudo tg_unblock --dns
|
||||||
|
```
|
||||||
|
|
||||||
|
Остановка — `Ctrl+C` (DNS автоматически сбросится).
|
||||||
|
|
||||||
|
```
|
||||||
|
$ tg_unblock --help
|
||||||
|
Обход блокировки Telegram через WebSocket-туннель
|
||||||
|
|
||||||
|
Usage: tg_unblock [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-p, --port <PORT> Порт SOCKS5-прокси [default: 1080]
|
||||||
|
-b, --bind <BIND> Адрес привязки [default: 127.0.0.1]
|
||||||
|
--dns Сменить DNS на Cloudflare 1.1.1.1 (нужен root/admin)
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version
|
||||||
|
```
|
||||||
|
|
||||||
|
### GUI (Windows)
|
||||||
|
|
||||||
|
1. Запустите `tg_unblock_gui.exe`
|
||||||
2. Нажмите **"Запустить обход"**
|
2. Нажмите **"Запустить обход"**
|
||||||
3. Нажмите **"Настроить автоматически"** — откроется Telegram, нажмите "Подключить"
|
3. Нажмите **"Настроить автоматически"** — откроется Telegram, нажмите "Подключить"
|
||||||
4. Готово. Telegram работает на полной скорости.
|
4. Готово. Telegram работает на полной скорости.
|
||||||
|
|
||||||
### Ручная настройка прокси
|
### Настройка прокси в Telegram
|
||||||
|
|
||||||
Если автонастройка не сработала:
|
|
||||||
|
|
||||||
**Telegram Desktop** → Настройки → Продвинутые → Тип соединения → **Использовать SOCKS5-прокси**
|
**Telegram Desktop** → Настройки → Продвинутые → Тип соединения → **Использовать SOCKS5-прокси**
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Сервер | `127.0.0.1` |
|
| Сервер | `127.0.0.1` |
|
||||||
| Порт | `1080` |
|
| Порт | `1080` (или тот, что указали в `--port`) |
|
||||||
| Логин | *пусто* |
|
| Логин | *пусто* |
|
||||||
| Пароль | *пусто* |
|
| Пароль | *пусто* |
|
||||||
|
|
||||||
|
|
@ -89,39 +127,46 @@ Telegram Desktop
|
||||||
|
|
||||||
| DC | Подсеть | WebSocket |
|
| DC | Подсеть | WebSocket |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| DC1 | `149.154.160.0/22` | `wss://pluto.web.telegram.org/apiws` |
|
| DC1 | `149.154.160.0/22` | `wss://kws1.web.telegram.org/apiws` |
|
||||||
| DC2 | `149.154.164.0/22` | `wss://venus.web.telegram.org/apiws` |
|
| DC2 | `149.154.164.0/22` | `wss://kws2.web.telegram.org/apiws` |
|
||||||
| DC3 | `149.154.168.0/22` | `wss://aurora.web.telegram.org/apiws` |
|
| DC3 | `149.154.168.0/22` | `wss://kws3.web.telegram.org/apiws` |
|
||||||
| DC4 | `91.108.12.0/22` | `wss://vesta.web.telegram.org/apiws` |
|
| DC4 | `91.108.12.0/22` | `wss://kws4.web.telegram.org/apiws` |
|
||||||
| DC5 | `91.108.56.0/22` | `wss://flora.web.telegram.org/apiws` |
|
| DC5 | `91.108.56.0/22` | `wss://kws5.web.telegram.org/apiws` |
|
||||||
|
|
||||||
Имена DC (`pluto`, `venus`, `aurora`, `vesta`, `flora`) — из [официальной документации MTProto](https://core.telegram.org/mtproto/transports).
|
|
||||||
|
|
||||||
## Стек
|
## Стек
|
||||||
|
|
||||||
| Что | Зачем |
|
| Что | Зачем |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Rust** | Скорость, безопасность, один бинарник без зависимостей |
|
| **Rust** | Скорость, безопасность, один бинарник без зависимостей |
|
||||||
| **egui / eframe** | Нативный GUI без Electron, без браузера |
|
|
||||||
| **tokio** | Async I/O для высокопроизводительного проксирования |
|
| **tokio** | Async I/O для высокопроизводительного проксирования |
|
||||||
| **tokio-tungstenite** | WebSocket-клиент с TLS |
|
| **tokio-tungstenite** | WebSocket-клиент с TLS |
|
||||||
| **native-tls** | TLS через системные сертификаты Windows |
|
| **native-tls** | TLS через системные сертификаты |
|
||||||
|
| **clap** | Парсинг аргументов CLI |
|
||||||
|
| **egui / eframe** | Нативный GUI (опционально, Windows) |
|
||||||
|
|
||||||
## Структура проекта
|
## Структура проекта
|
||||||
|
|
||||||
```
|
```
|
||||||
tglock/
|
tglock/
|
||||||
├── Cargo.toml # Зависимости
|
├── Cargo.toml # Зависимости и таргеты
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── main.rs # GUI + управление прокси
|
│ ├── lib.rs # Библиотечный крейт
|
||||||
│ ├── ws_proxy.rs # SOCKS5-сервер + WebSocket-туннель
|
│ ├── ws_proxy.rs # SOCKS5-сервер + WebSocket-туннель
|
||||||
│ ├── bypass.rs # DNS-настройка, утилиты Windows
|
│ ├── bypass.rs # DNS-настройка (Linux + Windows)
|
||||||
│ └── network.rs # Сетевая диагностика
|
│ ├── network.rs # Сетевая диагностика (Linux + Windows)
|
||||||
└── tg_blacklist.txt # IP-подсети и домены Telegram
|
│ └── bin/
|
||||||
|
│ ├── cli.rs # CLI-интерфейс (кроссплатформенный)
|
||||||
|
│ └── gui.rs # GUI-интерфейс (Windows)
|
||||||
|
└── tg_blacklist.txt # IP-подсети и домены Telegram
|
||||||
```
|
```
|
||||||
|
|
||||||
## Требования
|
## Требования
|
||||||
|
|
||||||
|
### CLI (Linux / macOS)
|
||||||
|
- [Rust 1.70+](https://rustup.rs/) (для сборки из исходников)
|
||||||
|
- Права root (для смены DNS с флагом `--dns`, опционально)
|
||||||
|
|
||||||
|
### GUI (Windows)
|
||||||
- Windows 10/11
|
- Windows 10/11
|
||||||
- [Rust 1.70+](https://rustup.rs/) (для сборки из исходников)
|
- [Rust 1.70+](https://rustup.rs/) (для сборки из исходников)
|
||||||
- Права администратора (для смены DNS, опционально)
|
- Права администратора (для смены DNS, опционально)
|
||||||
|
|
@ -140,6 +185,9 @@ A: Пока только Telegram Desktop. Для мобильных устро
|
||||||
**Q: Замедляется ли интернет?**
|
**Q: Замедляется ли интернет?**
|
||||||
A: Нет. Проксируется только трафик к серверам Telegram. Весь остальной трафик идёт напрямую.
|
A: Нет. Проксируется только трафик к серверам Telegram. Весь остальной трафик идёт напрямую.
|
||||||
|
|
||||||
|
**Q: Работает ли на Linux?**
|
||||||
|
A: Да. CLI-версия полностью кроссплатформенная. На Linux для смены DNS используется `resolvectl` (systemd-resolved) или прямая запись в `/etc/resolv.conf`.
|
||||||
|
|
||||||
## VPN для полного обхода
|
## VPN для полного обхода
|
||||||
|
|
||||||
Если нужен обход блокировок для **всех** приложений (YouTube, Discord, Instagram и др.) — попробуйте **[by sonic VPN](https://t.me/bysonicvpn_bot)**. Быстрый, без ограничений скорости.
|
Если нужен обход блокировок для **всех** приложений (YouTube, Discord, Instagram и др.) — попробуйте **[by sonic VPN](https://t.me/bysonicvpn_bot)**. Быстрый, без ограничений скорости.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use tg_unblock::{bypass, network, ws_proxy};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "tg_unblock", version, about = "Обход блокировки Telegram через WebSocket-туннель")]
|
||||||
|
struct Args {
|
||||||
|
/// Порт SOCKS5-прокси
|
||||||
|
#[arg(short, long, default_value_t = 1080)]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
|
/// Адрес привязки
|
||||||
|
#[arg(short, long, default_value = "127.0.0.1")]
|
||||||
|
bind: String,
|
||||||
|
|
||||||
|
/// Сменить DNS на Cloudflare 1.1.1.1 (нужен root/admin)
|
||||||
|
#[arg(long)]
|
||||||
|
dns: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let is_admin = bypass::check_admin();
|
||||||
|
let mut dns_was_set = false;
|
||||||
|
let mut adapter_name: Option<String> = None;
|
||||||
|
|
||||||
|
// DNS setup
|
||||||
|
if args.dns {
|
||||||
|
if !is_admin {
|
||||||
|
eprintln!("[!] Для смены DNS нужны права root/администратора");
|
||||||
|
} else {
|
||||||
|
adapter_name = network::detect_adapter();
|
||||||
|
if let Some(ref name) = adapter_name {
|
||||||
|
match bypass::set_dns(name, "1.1.1.1", "1.0.0.1") {
|
||||||
|
Ok(()) => {
|
||||||
|
bypass::flush_dns();
|
||||||
|
eprintln!("[+] DNS -> Cloudflare 1.1.1.1 (адаптер: {})", name);
|
||||||
|
dns_was_set = true;
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("[!] Не удалось сменить DNS: {}", e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("[!] Не удалось определить сетевой адаптер");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stats = ws_proxy::ProxyStats::new();
|
||||||
|
stats.verbose.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let stats_signal = stats.clone();
|
||||||
|
|
||||||
|
eprintln!("[*] Запускаю SOCKS5-прокси на {}:{}...", args.bind, args.port);
|
||||||
|
eprintln!("[*] Подключение прокси в Telegram:");
|
||||||
|
eprintln!(" tg://socks?server={}&port={}", args.bind, args.port);
|
||||||
|
eprintln!("[*] Ctrl+C для остановки");
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Runtime::new().expect("Не удалось создать tokio runtime");
|
||||||
|
|
||||||
|
rt.block_on(async {
|
||||||
|
// Graceful shutdown по Ctrl+C
|
||||||
|
let stats_ctrl = stats_signal.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::signal::ctrl_c().await.ok();
|
||||||
|
eprintln!("\n[*] Остановка...");
|
||||||
|
stats_ctrl.running.store(false, Ordering::SeqCst);
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(e) = ws_proxy::run_proxy_bind(&args.bind, args.port, stats_signal).await {
|
||||||
|
eprintln!("[!] Прокси остановлен с ошибкой: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup DNS
|
||||||
|
if dns_was_set {
|
||||||
|
let name = adapter_name.or_else(network::detect_adapter);
|
||||||
|
if let Some(ref name) = name {
|
||||||
|
let _ = bypass::reset_dns(name);
|
||||||
|
bypass::flush_dns();
|
||||||
|
eprintln!("[+] DNS сброшен");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("[*] Завершено. Всего соединений: {}", stats.total_conn.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
mod bypass;
|
|
||||||
mod network;
|
|
||||||
mod ws_proxy;
|
|
||||||
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use tg_unblock::{bypass, network, ws_proxy};
|
||||||
|
|
||||||
const PROXY_PORT: u16 = 1080;
|
const PROXY_PORT: u16 = 1080;
|
||||||
|
|
||||||
|
|
@ -30,6 +27,7 @@ fn main() -> eframe::Result<()> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
fn setup_fonts(ctx: &egui::Context) {
|
fn setup_fonts(ctx: &egui::Context) {
|
||||||
let mut fonts = egui::FontDefinitions::default();
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
fonts.font_data.insert(
|
fonts.font_data.insert(
|
||||||
|
|
@ -51,6 +49,9 @@ fn setup_fonts(ctx: &egui::Context) {
|
||||||
ctx.set_fonts(fonts);
|
ctx.set_fonts(fonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn setup_fonts(_ctx: &egui::Context) {}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct LogEntry {
|
struct LogEntry {
|
||||||
text: String,
|
text: String,
|
||||||
122
src/bypass.rs
122
src/bypass.rs
|
|
@ -1,6 +1,8 @@
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
// ===== Admin/root check =====
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn check_admin() -> bool {
|
pub fn check_admin() -> bool {
|
||||||
let output = Command::new("net")
|
let output = Command::new("net")
|
||||||
.args(["session"])
|
.args(["session"])
|
||||||
|
|
@ -8,6 +10,21 @@ pub fn check_admin() -> bool {
|
||||||
matches!(output, Ok(o) if o.status.success())
|
matches!(output, Ok(o) if o.status.success())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn check_admin() -> bool {
|
||||||
|
let output = Command::new("id").args(["-u"]).output();
|
||||||
|
match output {
|
||||||
|
Ok(o) => {
|
||||||
|
let uid = String::from_utf8_lossy(&o.stdout).trim().to_string();
|
||||||
|
uid == "0"
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== DNS management =====
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn set_dns(adapter: &str, primary: &str, secondary: &str) -> Result<(), String> {
|
pub fn set_dns(adapter: &str, primary: &str, secondary: &str) -> Result<(), String> {
|
||||||
let out1 = Command::new("netsh")
|
let out1 = Command::new("netsh")
|
||||||
.args([
|
.args([
|
||||||
|
|
@ -37,6 +54,31 @@ pub fn set_dns(adapter: &str, primary: &str, secondary: &str) -> Result<(), Stri
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn set_dns(interface: &str, primary: &str, secondary: &str) -> Result<(), String> {
|
||||||
|
// Try resolvectl first (systemd-resolved)
|
||||||
|
if has_command("resolvectl") {
|
||||||
|
let out = Command::new("resolvectl")
|
||||||
|
.args(["dns", interface, primary, secondary])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("resolvectl error: {}", e))?;
|
||||||
|
|
||||||
|
if out.status.success() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
|
return Err(format!("resolvectl failed: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: write /etc/resolv.conf directly
|
||||||
|
backup_resolv_conf()?;
|
||||||
|
let content = format!("# Set by tg_unblock\nnameserver {}\nnameserver {}\n", primary, secondary);
|
||||||
|
std::fs::write("/etc/resolv.conf", content)
|
||||||
|
.map_err(|e| format!("Failed to write /etc/resolv.conf: {}", e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn reset_dns(adapter: &str) -> Result<(), String> {
|
pub fn reset_dns(adapter: &str) -> Result<(), String> {
|
||||||
let out = Command::new("netsh")
|
let out = Command::new("netsh")
|
||||||
.args([
|
.args([
|
||||||
|
|
@ -53,12 +95,79 @@ pub fn reset_dns(adapter: &str) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn reset_dns(interface: &str) -> Result<(), String> {
|
||||||
|
if has_command("resolvectl") {
|
||||||
|
let out = Command::new("resolvectl")
|
||||||
|
.args(["revert", interface])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("resolvectl error: {}", e))?;
|
||||||
|
|
||||||
|
if out.status.success() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: restore backup
|
||||||
|
restore_resolv_conf()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn flush_dns() {
|
pub fn flush_dns() {
|
||||||
let _ = Command::new("ipconfig")
|
let _ = Command::new("ipconfig")
|
||||||
.args(["/flushdns"])
|
.args(["/flushdns"])
|
||||||
.output();
|
.output();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn flush_dns() {
|
||||||
|
if has_command("resolvectl") {
|
||||||
|
let _ = Command::new("resolvectl")
|
||||||
|
.args(["flush-caches"])
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Linux helpers =====
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn has_command(name: &str) -> bool {
|
||||||
|
Command::new("which")
|
||||||
|
.arg(name)
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn backup_resolv_conf() -> Result<(), String> {
|
||||||
|
let backup = "/etc/resolv.conf.tg_unblock_bak";
|
||||||
|
if !std::path::Path::new(backup).exists() {
|
||||||
|
std::fs::copy("/etc/resolv.conf", backup)
|
||||||
|
.map_err(|e| format!("Failed to backup resolv.conf: {}", e))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn restore_resolv_conf() -> Result<(), String> {
|
||||||
|
let backup = "/etc/resolv.conf.tg_unblock_bak";
|
||||||
|
if std::path::Path::new(backup).exists() {
|
||||||
|
std::fs::copy(backup, "/etc/resolv.conf")
|
||||||
|
.map_err(|e| format!("Failed to restore resolv.conf: {}", e))?;
|
||||||
|
let _ = std::fs::remove_file(backup);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("No resolv.conf backup found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Windows-only: GoodbyeDPI =====
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn find_goodbyedpi() -> Option<String> {
|
pub fn find_goodbyedpi() -> Option<String> {
|
||||||
let exe_dir = std::env::current_exe()
|
let exe_dir = std::env::current_exe()
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -75,7 +184,6 @@ pub fn find_goodbyedpi() -> Option<String> {
|
||||||
];
|
];
|
||||||
|
|
||||||
for dir in &search_dirs {
|
for dir in &search_dirs {
|
||||||
// Check common locations
|
|
||||||
for sub in &["x86_64", "x86", ""] {
|
for sub in &["x86_64", "x86", ""] {
|
||||||
let candidate = if sub.is_empty() {
|
let candidate = if sub.is_empty() {
|
||||||
dir.join("goodbyedpi.exe")
|
dir.join("goodbyedpi.exe")
|
||||||
|
|
@ -88,7 +196,6 @@ pub fn find_goodbyedpi() -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursive search in tools/
|
|
||||||
if let Ok(entries) = find_file_recursive(Path::new("tools"), "goodbyedpi.exe") {
|
if let Ok(entries) = find_file_recursive(Path::new("tools"), "goodbyedpi.exe") {
|
||||||
if !entries.is_empty() {
|
if !entries.is_empty() {
|
||||||
return Some(entries[0].to_string_lossy().to_string());
|
return Some(entries[0].to_string_lossy().to_string());
|
||||||
|
|
@ -98,6 +205,7 @@ pub fn find_goodbyedpi() -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
fn find_file_recursive(dir: &Path, filename: &str) -> Result<Vec<PathBuf>, std::io::Error> {
|
fn find_file_recursive(dir: &Path, filename: &str) -> Result<Vec<PathBuf>, std::io::Error> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
if !dir.exists() {
|
if !dir.exists() {
|
||||||
|
|
@ -115,6 +223,7 @@ fn find_file_recursive(dir: &Path, filename: &str) -> Result<Vec<PathBuf>, std::
|
||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_blacklist_path() -> Option<String> {
|
pub fn get_blacklist_path() -> Option<String> {
|
||||||
let candidates = vec![
|
let candidates = vec![
|
||||||
PathBuf::from("tg_blacklist.txt"),
|
PathBuf::from("tg_blacklist.txt"),
|
||||||
|
|
@ -133,6 +242,7 @@ pub fn get_blacklist_path() -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn start_goodbyedpi(exe_path: &str, args: &[&str], blacklist: Option<&str>) -> Result<(), String> {
|
pub fn start_goodbyedpi(exe_path: &str, args: &[&str], blacklist: Option<&str>) -> Result<(), String> {
|
||||||
let mut cmd = Command::new(exe_path);
|
let mut cmd = Command::new(exe_path);
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
|
|
@ -145,12 +255,14 @@ pub fn start_goodbyedpi(exe_path: &str, args: &[&str], blacklist: Option<&str>)
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn kill_goodbyedpi() {
|
pub fn kill_goodbyedpi() {
|
||||||
let _ = Command::new("taskkill")
|
let _ = Command::new("taskkill")
|
||||||
.args(["/f", "/im", "goodbyedpi.exe"])
|
.args(["/f", "/im", "goodbyedpi.exe"])
|
||||||
.output();
|
.output();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn download_goodbyedpi() -> Result<String, String> {
|
pub fn download_goodbyedpi() -> Result<String, String> {
|
||||||
let tools_dir = PathBuf::from("tools");
|
let tools_dir = PathBuf::from("tools");
|
||||||
std::fs::create_dir_all(&tools_dir)
|
std::fs::create_dir_all(&tools_dir)
|
||||||
|
|
@ -159,7 +271,6 @@ pub fn download_goodbyedpi() -> Result<String, String> {
|
||||||
let zip_path = tools_dir.join("goodbyedpi.zip");
|
let zip_path = tools_dir.join("goodbyedpi.zip");
|
||||||
let url = "https://github.com/ValdikSS/GoodbyeDPI/releases/download/0.2.3rc3/goodbyedpi-0.2.3rc3-2.zip";
|
let url = "https://github.com/ValdikSS/GoodbyeDPI/releases/download/0.2.3rc3/goodbyedpi-0.2.3rc3-2.zip";
|
||||||
|
|
||||||
// Download using powershell
|
|
||||||
let dl_script = format!(
|
let dl_script = format!(
|
||||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '{}' -OutFile '{}' -UseBasicParsing",
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '{}' -OutFile '{}' -UseBasicParsing",
|
||||||
url,
|
url,
|
||||||
|
|
@ -176,7 +287,6 @@ pub fn download_goodbyedpi() -> Result<String, String> {
|
||||||
return Err(format!("Download failed: {}", stderr));
|
return Err(format!("Download failed: {}", stderr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract
|
|
||||||
let extract_script = format!(
|
let extract_script = format!(
|
||||||
"Expand-Archive -Path '{}' -DestinationPath '{}' -Force",
|
"Expand-Archive -Path '{}' -DestinationPath '{}' -Force",
|
||||||
zip_path.to_string_lossy(),
|
zip_path.to_string_lossy(),
|
||||||
|
|
@ -193,9 +303,7 @@ pub fn download_goodbyedpi() -> Result<String, String> {
|
||||||
return Err(format!("Extraction failed: {}", stderr));
|
return Err(format!("Extraction failed: {}", stderr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up zip
|
|
||||||
let _ = std::fs::remove_file(&zip_path);
|
let _ = std::fs::remove_file(&zip_path);
|
||||||
|
|
||||||
// Find the exe
|
|
||||||
find_goodbyedpi().ok_or_else(|| "goodbyedpi.exe not found after extraction".to_string())
|
find_goodbyedpi().ok_or_else(|| "goodbyedpi.exe not found after extraction".to_string())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod bypass;
|
||||||
|
pub mod network;
|
||||||
|
pub mod ws_proxy;
|
||||||
|
|
@ -2,6 +2,9 @@ use std::net::{TcpStream, SocketAddr};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
// ===== Adapter detection =====
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn detect_adapter() -> Option<String> {
|
pub fn detect_adapter() -> Option<String> {
|
||||||
let output = Command::new("powershell")
|
let output = Command::new("powershell")
|
||||||
.args([
|
.args([
|
||||||
|
|
@ -19,6 +22,28 @@ pub fn detect_adapter() -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn detect_adapter() -> Option<String> {
|
||||||
|
// Parse default route: "default via 10.0.0.1 dev eth0 ..."
|
||||||
|
let output = Command::new("ip")
|
||||||
|
.args(["route", "show", "default"])
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in stdout.lines() {
|
||||||
|
if let Some(pos) = line.find("dev ") {
|
||||||
|
let after = &line[pos + 4..];
|
||||||
|
let iface = after.split_whitespace().next()?;
|
||||||
|
return Some(iface.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Current DNS =====
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_current_dns() -> Option<String> {
|
pub fn get_current_dns() -> Option<String> {
|
||||||
let output = Command::new("powershell")
|
let output = Command::new("powershell")
|
||||||
.args([
|
.args([
|
||||||
|
|
@ -36,20 +61,56 @@ pub fn get_current_dns() -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn get_current_dns() -> Option<String> {
|
||||||
|
let content = std::fs::read_to_string("/etc/resolv.conf").ok()?;
|
||||||
|
let servers: Vec<&str> = content
|
||||||
|
.lines()
|
||||||
|
.filter(|l| l.starts_with("nameserver"))
|
||||||
|
.filter_map(|l| l.split_whitespace().nth(1))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if servers.is_empty() {
|
||||||
|
Some("Не определено".to_string())
|
||||||
|
} else {
|
||||||
|
Some(servers.join(", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Ping =====
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn ping_host(ip: &str) -> (bool, Option<u64>) {
|
pub fn ping_host(ip: &str) -> (bool, Option<u64>) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let output = Command::new("ping")
|
let output = Command::new("ping")
|
||||||
.args(["-n", "1", "-w", "3000", ip])
|
.args(["-n", "1", "-w", "3000", ip])
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
|
parse_ping_output(output, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn ping_host(ip: &str) -> (bool, Option<u64>) {
|
||||||
|
let start = Instant::now();
|
||||||
|
let output = Command::new("ping")
|
||||||
|
.args(["-c", "1", "-W", "3", ip])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
parse_ping_output(output, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ping_output(
|
||||||
|
output: Result<std::process::Output, std::io::Error>,
|
||||||
|
start: Instant,
|
||||||
|
) -> (bool, Option<u64>) {
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
let elapsed = start.elapsed().as_millis() as u64;
|
let elapsed = start.elapsed().as_millis() as u64;
|
||||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
let ok = out.status.success() && (stdout.contains("TTL=") || stdout.contains("ttl="));
|
let ok = out.status.success()
|
||||||
|
&& (stdout.contains("TTL=") || stdout.contains("ttl="));
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
// Try to extract actual time from ping output
|
|
||||||
if let Some(time_str) = extract_ping_time(&stdout) {
|
if let Some(time_str) = extract_ping_time(&stdout) {
|
||||||
(true, Some(time_str))
|
(true, Some(time_str))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -64,7 +125,6 @@ pub fn ping_host(ip: &str) -> (bool, Option<u64>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_ping_time(output: &str) -> Option<u64> {
|
fn extract_ping_time(output: &str) -> Option<u64> {
|
||||||
// Match patterns like "time=46ms" or "time<1ms" or "время=46мс"
|
|
||||||
for line in output.lines() {
|
for line in output.lines() {
|
||||||
let lower = line.to_lowercase();
|
let lower = line.to_lowercase();
|
||||||
if let Some(pos) = lower.find("time=").or_else(|| lower.find("time<")) {
|
if let Some(pos) = lower.find("time=").or_else(|| lower.find("time<")) {
|
||||||
|
|
@ -88,6 +148,8 @@ fn extract_ping_time(output: &str) -> Option<u64> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== TCP / HTTPS checks (cross-platform) =====
|
||||||
|
|
||||||
pub fn tcp_check(ip: &str, port: u16) -> (bool, Option<u64>) {
|
pub fn tcp_check(ip: &str, port: u16) -> (bool, Option<u64>) {
|
||||||
let addr: SocketAddr = format!("{}:{}", ip, port).parse().unwrap();
|
let addr: SocketAddr = format!("{}:{}", ip, port).parse().unwrap();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
@ -119,9 +181,6 @@ pub fn https_check(url: &str) -> (bool, Option<u64>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Benchmarks Telegram connectivity: runs multiple TCP+HTTPS checks,
|
|
||||||
/// returns (works: bool, score: u64) where lower score = faster connection.
|
|
||||||
/// Score is average latency across all successful checks. u64::MAX if nothing works.
|
|
||||||
pub fn benchmark_telegram() -> (bool, u64) {
|
pub fn benchmark_telegram() -> (bool, u64) {
|
||||||
let tcp_targets = [
|
let tcp_targets = [
|
||||||
("149.154.167.51", 443u16),
|
("149.154.167.51", 443u16),
|
||||||
|
|
@ -134,7 +193,6 @@ pub fn benchmark_telegram() -> (bool, u64) {
|
||||||
let mut ok_count: u64 = 0;
|
let mut ok_count: u64 = 0;
|
||||||
let mut fail_count: u64 = 0;
|
let mut fail_count: u64 = 0;
|
||||||
|
|
||||||
// TCP checks (x2 rounds for stability)
|
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
for (ip, port) in &tcp_targets {
|
for (ip, port) in &tcp_targets {
|
||||||
let (ok, latency) = tcp_check(ip, *port);
|
let (ok, latency) = tcp_check(ip, *port);
|
||||||
|
|
@ -147,7 +205,6 @@ pub fn benchmark_telegram() -> (bool, u64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS check — the real indicator of usable speed
|
|
||||||
let https_urls = [
|
let https_urls = [
|
||||||
"https://web.telegram.org",
|
"https://web.telegram.org",
|
||||||
"https://t.me",
|
"https://t.me",
|
||||||
|
|
@ -155,7 +212,6 @@ pub fn benchmark_telegram() -> (bool, u64) {
|
||||||
for url in &https_urls {
|
for url in &https_urls {
|
||||||
let (ok, latency) = https_check(url);
|
let (ok, latency) = https_check(url);
|
||||||
if ok {
|
if ok {
|
||||||
// Weight HTTPS 3x heavier since it's closer to real usage
|
|
||||||
let ms = latency.unwrap_or(10000);
|
let ms = latency.unwrap_or(10000);
|
||||||
total_ms += ms * 3;
|
total_ms += ms * 3;
|
||||||
ok_count += 3;
|
ok_count += 3;
|
||||||
|
|
@ -168,7 +224,6 @@ pub fn benchmark_telegram() -> (bool, u64) {
|
||||||
return (false, u64::MAX);
|
return (false, u64::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Penalize failures: each fail adds 2000ms to the score
|
|
||||||
let penalty = fail_count * 2000;
|
let penalty = fail_count * 2000;
|
||||||
let avg = (total_ms + penalty) / (ok_count + fail_count);
|
let avg = (total_ms + penalty) / (ok_count + fail_count);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub struct ProxyStats {
|
||||||
pub active_conn: AtomicU32,
|
pub active_conn: AtomicU32,
|
||||||
pub total_conn: AtomicU32,
|
pub total_conn: AtomicU32,
|
||||||
pub ws_active: AtomicU32,
|
pub ws_active: AtomicU32,
|
||||||
|
pub verbose: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProxyStats {
|
impl ProxyStats {
|
||||||
|
|
@ -20,12 +21,17 @@ impl ProxyStats {
|
||||||
active_conn: AtomicU32::new(0),
|
active_conn: AtomicU32::new(0),
|
||||||
total_conn: AtomicU32::new(0),
|
total_conn: AtomicU32::new(0),
|
||||||
ws_active: AtomicU32::new(0),
|
ws_active: AtomicU32::new(0),
|
||||||
|
verbose: AtomicBool::new(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_proxy(port: u16, stats: Arc<ProxyStats>) -> Result<(), String> {
|
pub async fn run_proxy(port: u16, stats: Arc<ProxyStats>) -> Result<(), String> {
|
||||||
let addr = format!("127.0.0.1:{}", port);
|
run_proxy_bind("127.0.0.1", port, stats).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_proxy_bind(bind: &str, port: u16, stats: Arc<ProxyStats>) -> Result<(), String> {
|
||||||
|
let addr = format!("{}:{}", bind, port);
|
||||||
let listener = TcpListener::bind(&addr)
|
let listener = TcpListener::bind(&addr)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Не удалось занять порт {}: {}", port, e))?;
|
.map_err(|e| format!("Не удалось занять порт {}: {}", port, e))?;
|
||||||
|
|
@ -143,7 +149,7 @@ async fn handle_socks5(
|
||||||
|
|
||||||
let (dest_addr, dest_port) = parse_dest(&buf[3..n])?;
|
let (dest_addr, dest_port) = parse_dest(&buf[3..n])?;
|
||||||
let is_tg = is_telegram_ip(&dest_addr);
|
let is_tg = is_telegram_ip(&dest_addr);
|
||||||
|
let verbose = stats.verbose.load(Ordering::Relaxed);
|
||||||
// SOCKS5 success (we handle the connection ourselves)
|
// SOCKS5 success (we handle the connection ourselves)
|
||||||
stream
|
stream
|
||||||
.write_all(&[0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x04, 0x38])
|
.write_all(&[0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x04, 0x38])
|
||||||
|
|
@ -163,6 +169,10 @@ async fn handle_socks5(
|
||||||
.unwrap_or(2)
|
.unwrap_or(2)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
eprintln!("[+] Telegram {}:{} -> WSS DC{}", dest_addr, dest_port, dc);
|
||||||
|
}
|
||||||
|
|
||||||
stats.ws_active.fetch_add(1, Ordering::Relaxed);
|
stats.ws_active.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
// Try WebSocket tunnel; fall back to direct TCP on failure
|
// Try WebSocket tunnel; fall back to direct TCP on failure
|
||||||
|
|
@ -170,12 +180,19 @@ async fn handle_socks5(
|
||||||
|
|
||||||
stats.ws_active.fetch_sub(1, Ordering::Relaxed);
|
stats.ws_active.fetch_sub(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
eprintln!("[-] Telegram DC{} отключён", dc);
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = ws_result {
|
if let Err(e) = ws_result {
|
||||||
return Err(format!("DC{} tunnel: {}", dc, e).into());
|
return Err(format!("DC{} tunnel: {}", dc, e).into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Non-Telegram — direct TCP passthrough
|
// Non-Telegram — direct TCP passthrough
|
||||||
let target = format!("{}:{}", dest_addr, dest_port);
|
let target = format!("{}:{}", dest_addr, dest_port);
|
||||||
|
if verbose {
|
||||||
|
eprintln!("[+] TCP {}:{}", dest_addr, dest_port);
|
||||||
|
}
|
||||||
match TcpStream::connect(&target).await {
|
match TcpStream::connect(&target).await {
|
||||||
Ok(remote) => {
|
Ok(remote) => {
|
||||||
let _ = remote.set_nodelay(true);
|
let _ = remote.set_nodelay(true);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue