TG Unblock v0.2.0: Telegram WebSocket bypass proxy

Made-with: Cursor:
This commit is contained in:
by-sonic 2026-03-16 23:54:42 +03:00
commit d0646af85d
11 changed files with 2146 additions and 0 deletions

64
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Build & Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build release
run: cargo build --release
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tg_unblock-windows-x64
path: target/release/tg_unblock.exe
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: tg_unblock-windows-x64
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: tg_unblock.exe
generate_release_notes: true
body: |
## TG Unblock ${{ github.ref_name }}
Обход блокировки Telegram через WebSocket-туннель.
### Установка
1. Скачайте `tg_unblock.exe`
2. Запустите
3. Нажмите "Запустить обход"
4. Нажмите "Настроить автоматически"
---
**[by sonic VPN](https://t.me/bysonicvpn_bot)** — полный обход блокировок для всех приложений

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
/tools
*.zip
*.exe
!*.rs
Cargo.lock

23
Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "tg_unblock"
version = "0.2.0"
edition = "2021"
[dependencies]
eframe = "0.31"
egui = "0.31"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
open = "5"
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
native-tls = "0.2"
futures-util = "0.3"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser"] }
[[bin]]
name = "tg_unblock"
path = "src/main.rs"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 by sonic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

159
README.md Normal file
View File

@ -0,0 +1,159 @@
<p align="center">
<h1 align="center">TG Unblock</h1>
<p align="center">
<b>Обход блокировки Telegram через WebSocket-туннель</b><br>
Без VPN. Без серверов. Без абонентки. Один клик.
</p>
<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/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>
<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">
</p>
</p>
---
## Что это?
**TG Unblock** — десктопное приложение на Rust, которое обходит блокировку Telegram через локальный WebSocket-прокси. Провайдер видит обычный HTTPS к `web.telegram.org`, а не MTProto — DPI не может обнаружить и заблокировать трафик.
### Почему не GoodbyeDPI / Zapret?
| | GoodbyeDPI | Zapret | **TG Unblock** |
|---|---|---|---|
| Метод | Фрагментация пакетов | Desync пакетов | WebSocket-туннель |
| DPI видит MTProto? | Нет (обфускация) | Нет (desync) | **Нет (обычный HTTPS)** |
| IP-шейпинг обходит? | Нет | Нет | **Да** |
| Скорость | Зависит от DPI | Зависит от DPI | **Полная** |
| Переподключения | Возможны | Возможны | **Нет** |
| Настройка | Много параметров | Стратегии | **Один клик** |
## Скачать
> **[Скачать последний релиз](https://github.com/by-sonic/tglock/releases)**
Или собрать из исходников:
```bash
git clone https://github.com/by-sonic/tglock.git
cd tglock
cargo build --release
```
Готовый `.exe` будет в `target/release/tg_unblock.exe`.
## Как пользоваться
1. Запустите `tg_unblock.exe`
2. Нажмите **"Запустить обход"**
3. Нажмите **"Настроить автоматически"** — откроется Telegram, нажмите "Подключить"
4. Готово. Telegram работает на полной скорости.
### Ручная настройка прокси
Если автонастройка не сработала:
**Telegram Desktop** → Настройки → Продвинутые → Тип соединения → **Использовать SOCKS5-прокси**
| Параметр | Значение |
|---|---|
| Сервер | `127.0.0.1` |
| Порт | `1080` |
| Логин | *пусто* |
| Пароль | *пусто* |
## Как это работает
```
Telegram Desktop
▼ (SOCKS5)
┌──────────────────┐
│ TG Unblock │ 127.0.0.1:1080
│ WS-прокси │
└──────┬───────────┘
▼ (определяет DC по IP)
├── Telegram IP? ──► WSS-туннель к {dc}.web.telegram.org/apiws
│ (провайдер видит обычный HTTPS)
└── Другой IP? ────► Прямое TCP-соединение (без изменений)
```
### DC-маппинг
Приложение автоматически определяет Data Center по IP-адресу и маршрутизирует через правильный WebSocket-эндпоинт:
| DC | Подсеть | WebSocket |
|---|---|---|
| DC1 | `149.154.160.0/22` | `wss://pluto.web.telegram.org/apiws` |
| DC2 | `149.154.164.0/22` | `wss://venus.web.telegram.org/apiws` |
| DC3 | `149.154.168.0/22` | `wss://aurora.web.telegram.org/apiws` |
| DC4 | `91.108.12.0/22` | `wss://vesta.web.telegram.org/apiws` |
| DC5 | `91.108.56.0/22` | `wss://flora.web.telegram.org/apiws` |
Имена DC (`pluto`, `venus`, `aurora`, `vesta`, `flora`) — из [официальной документации MTProto](https://core.telegram.org/mtproto/transports).
## Стек
| Что | Зачем |
|---|---|
| **Rust** | Скорость, безопасность, один бинарник без зависимостей |
| **egui / eframe** | Нативный GUI без Electron, без браузера |
| **tokio** | Async I/O для высокопроизводительного проксирования |
| **tokio-tungstenite** | WebSocket-клиент с TLS |
| **native-tls** | TLS через системные сертификаты Windows |
## Структура проекта
```
tglock/
├── Cargo.toml # Зависимости
├── src/
│ ├── main.rs # GUI + управление прокси
│ ├── ws_proxy.rs # SOCKS5-сервер + WebSocket-туннель
│ ├── bypass.rs # DNS-настройка, утилиты Windows
│ └── network.rs # Сетевая диагностика
└── tg_blacklist.txt # IP-подсети и домены Telegram
```
## Требования
- Windows 10/11
- [Rust 1.70+](https://rustup.rs/) (для сборки из исходников)
- Права администратора (для смены DNS, опционально)
## FAQ
**Q: Это VPN?**
A: Нет. Трафик не идёт через сторонние серверы. Прокси работает локально и туннелирует только Telegram-трафик через WebSocket к официальным серверам Telegram.
**Q: Это безопасно?**
A: Весь код открыт. Никакой телеметрии. Никаких данных не отправляется. Соединение с Telegram остаётся end-to-end зашифрованным (MTProto).
**Q: Будет ли работать с мобильным Telegram?**
A: Пока только Telegram Desktop. Для мобильных устройств рекомендуем [by sonic VPN](https://t.me/bysonicvpn_bot).
**Q: Замедляется ли интернет?**
A: Нет. Проксируется только трафик к серверам Telegram. Весь остальной трафик идёт напрямую.
## VPN для полного обхода
Если нужен обход блокировок для **всех** приложений (YouTube, Discord, Instagram и др.) — попробуйте **[by sonic VPN](https://t.me/bysonicvpn_bot)**. Быстрый, без ограничений скорости.
## Лицензия
MIT — делайте что хотите.
## Автор
**by sonic** — [@bysonicvpn_bot](https://t.me/bysonicvpn_bot)
---
<p align="center">
<b>Если пригодилось — поставьте ⭐ на GitHub</b>
</p>

201
src/bypass.rs Normal file
View File

@ -0,0 +1,201 @@
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn check_admin() -> bool {
let output = Command::new("net")
.args(["session"])
.output();
matches!(output, Ok(o) if o.status.success())
}
pub fn set_dns(adapter: &str, primary: &str, secondary: &str) -> Result<(), String> {
let out1 = Command::new("netsh")
.args([
"interface", "ipv4", "set", "dnsservers",
adapter, "static", primary, "primary", "validate=no",
])
.output()
.map_err(|e| format!("netsh error: {}", e))?;
if !out1.status.success() {
let stderr = String::from_utf8_lossy(&out1.stderr);
return Err(format!("Failed to set primary DNS: {}", stderr));
}
let out2 = Command::new("netsh")
.args([
"interface", "ipv4", "add", "dnsservers",
adapter, secondary, "index=2", "validate=no",
])
.output()
.map_err(|e| format!("netsh error: {}", e))?;
if !out2.status.success() {
// Non-critical: secondary DNS may already exist
}
Ok(())
}
pub fn reset_dns(adapter: &str) -> Result<(), String> {
let out = Command::new("netsh")
.args([
"interface", "ipv4", "set", "dnsservers",
adapter, "dhcp",
])
.output()
.map_err(|e| format!("netsh error: {}", e))?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
return Err(format!("Failed to reset DNS: {}", stderr));
}
Ok(())
}
pub fn flush_dns() {
let _ = Command::new("ipconfig")
.args(["/flushdns"])
.output();
}
pub fn find_goodbyedpi() -> Option<String> {
let exe_dir = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
.unwrap_or_else(|| PathBuf::from("."));
let search_dirs = vec![
exe_dir.join("tools"),
exe_dir.join("tools").join("goodbyedpi"),
exe_dir.clone(),
PathBuf::from("tools"),
PathBuf::from("tools").join("goodbyedpi"),
PathBuf::from("."),
];
for dir in &search_dirs {
// Check common locations
for sub in &["x86_64", "x86", ""] {
let candidate = if sub.is_empty() {
dir.join("goodbyedpi.exe")
} else {
dir.join(sub).join("goodbyedpi.exe")
};
if candidate.exists() {
return Some(candidate.to_string_lossy().to_string());
}
}
}
// Recursive search in tools/
if let Ok(entries) = find_file_recursive(Path::new("tools"), "goodbyedpi.exe") {
if !entries.is_empty() {
return Some(entries[0].to_string_lossy().to_string());
}
}
None
}
fn find_file_recursive(dir: &Path, filename: &str) -> Result<Vec<PathBuf>, std::io::Error> {
let mut results = Vec::new();
if !dir.exists() {
return Ok(results);
}
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.file_name().map(|n| n == filename).unwrap_or(false) {
results.push(path);
} else if path.is_dir() {
results.extend(find_file_recursive(&path, filename)?);
}
}
Ok(results)
}
pub fn get_blacklist_path() -> Option<String> {
let candidates = vec![
PathBuf::from("tg_blacklist.txt"),
PathBuf::from("tools").join("tg_blacklist.txt"),
std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.join("tg_blacklist.txt")))
.unwrap_or_default(),
];
for path in candidates {
if path.exists() {
return Some(path.to_string_lossy().to_string());
}
}
None
}
pub fn start_goodbyedpi(exe_path: &str, args: &[&str], blacklist: Option<&str>) -> Result<(), String> {
let mut cmd = Command::new(exe_path);
cmd.args(args);
if let Some(bl) = blacklist {
cmd.args(["--blacklist", bl]);
}
cmd.spawn().map_err(|e| format!("Failed to start GoodbyeDPI: {}", e))?;
Ok(())
}
pub fn kill_goodbyedpi() {
let _ = Command::new("taskkill")
.args(["/f", "/im", "goodbyedpi.exe"])
.output();
}
pub fn download_goodbyedpi() -> Result<String, String> {
let tools_dir = PathBuf::from("tools");
std::fs::create_dir_all(&tools_dir)
.map_err(|e| format!("Cannot create tools dir: {}", e))?;
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";
// Download using powershell
let dl_script = format!(
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '{}' -OutFile '{}' -UseBasicParsing",
url,
zip_path.to_string_lossy()
);
let output = Command::new("powershell")
.args(["-Command", &dl_script])
.output()
.map_err(|e| format!("Download failed: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Download failed: {}", stderr));
}
// Extract
let extract_script = format!(
"Expand-Archive -Path '{}' -DestinationPath '{}' -Force",
zip_path.to_string_lossy(),
tools_dir.to_string_lossy()
);
let output = Command::new("powershell")
.args(["-Command", &extract_script])
.output()
.map_err(|e| format!("Extraction failed: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Extraction failed: {}", stderr));
}
// Clean up zip
let _ = std::fs::remove_file(&zip_path);
// Find the exe
find_goodbyedpi().ok_or_else(|| "goodbyedpi.exe not found after extraction".to_string())
}

358
src/main.rs Normal file
View File

@ -0,0 +1,358 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod bypass;
mod network;
mod ws_proxy;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use eframe::egui;
const PROXY_PORT: u16 = 1080;
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([680.0, 560.0])
.with_min_inner_size([580.0, 460.0])
.with_title("TG Unblock"),
..Default::default()
};
eframe::run_native(
"TG Unblock",
options,
Box::new(|cc| {
setup_fonts(&cc.egui_ctx);
Ok(Box::new(App::new()))
}),
)
}
fn setup_fonts(ctx: &egui::Context) {
let mut fonts = egui::FontDefinitions::default();
fonts.font_data.insert(
"system".to_owned(),
std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
"C:\\Windows\\Fonts\\segoeui.ttf"
))),
);
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "system".to_owned());
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "system".to_owned());
ctx.set_fonts(fonts);
}
#[derive(Clone)]
struct LogEntry {
text: String,
is_error: bool,
ts: String,
}
struct App {
log: Arc<Mutex<Vec<LogEntry>>>,
proxy_stats: Arc<ws_proxy::ProxyStats>,
is_admin: bool,
adapter_name: Arc<Mutex<Option<String>>>,
dns_set: Arc<Mutex<bool>>,
}
impl App {
fn new() -> Self {
let is_admin = bypass::check_admin();
let app = Self {
log: Arc::new(Mutex::new(Vec::new())),
proxy_stats: ws_proxy::ProxyStats::new(),
is_admin,
adapter_name: Arc::new(Mutex::new(None)),
dns_set: Arc::new(Mutex::new(false)),
};
log_msg(&app.log, "Запущено", false);
if !is_admin {
log_msg(&app.log, "Нет прав администратора — DNS менять не получится", true);
}
{
let adapter = app.adapter_name.clone();
let log = app.log.clone();
std::thread::spawn(move || {
if let Some(name) = network::detect_adapter() {
log_msg(&log, &format!("Адаптер: {}", name), false);
*adapter.lock().unwrap() = Some(name);
}
});
}
app
}
fn proxy_running(&self) -> bool {
self.proxy_stats.running.load(Ordering::SeqCst)
}
fn start_proxy(&self) {
if self.proxy_running() {
return;
}
let stats = self.proxy_stats.clone();
let log = self.log.clone();
let adapter = self.adapter_name.clone();
let dns_set = self.dns_set.clone();
let is_admin = self.is_admin;
std::thread::spawn(move || {
// DNS
if is_admin {
let aname = adapter.lock().unwrap().clone().or_else(network::detect_adapter);
if let Some(ref name) = aname {
if bypass::set_dns(name, "1.1.1.1", "1.0.0.1").is_ok() {
bypass::flush_dns();
log_msg(&log, "DNS → Cloudflare 1.1.1.1", false);
*dns_set.lock().unwrap() = true;
}
}
}
log_msg(&log, &format!("Запускаю WS-прокси на 127.0.0.1:{}...", PROXY_PORT), false);
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(ws_proxy::run_proxy(PROXY_PORT, stats));
if let Err(e) = result {
log_msg(&log, &format!("Прокси остановлен: {}", e), true);
}
});
std::thread::sleep(std::time::Duration::from_millis(300));
if self.proxy_running() {
log_msg(&self.log, "Прокси запущен! Настройте Telegram.", false);
}
}
fn stop_proxy(&self) {
self.proxy_stats.running.store(false, Ordering::SeqCst);
log_msg(&self.log, "Прокси остановлен", false);
if *self.dns_set.lock().unwrap() {
let adapter = self.adapter_name.clone();
let log = self.log.clone();
let dns_set = self.dns_set.clone();
std::thread::spawn(move || {
let aname = adapter.lock().unwrap().clone().or_else(network::detect_adapter);
if let Some(ref name) = aname {
let _ = bypass::reset_dns(name);
bypass::flush_dns();
*dns_set.lock().unwrap() = false;
log_msg(&log, "DNS сброшен", false);
}
});
}
}
fn open_tg_proxy_link(&self) {
let url = format!("tg://socks?server=127.0.0.1&port={}", PROXY_PORT);
log_msg(&self.log, "Открываю настройку прокси в Telegram...", false);
let _ = open::that(&url);
}
}
fn log_msg(log: &Arc<Mutex<Vec<LogEntry>>>, text: &str, err: bool) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let ts = format!("{:02}:{:02}:{:02}", (now % 86400) / 3600, (now % 3600) / 60, now % 60);
log.lock().unwrap().push(LogEntry {
text: text.to_string(),
is_error: err,
ts,
});
}
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.request_repaint_after(std::time::Duration::from_millis(400));
let running = self.proxy_running();
let active = self.proxy_stats.active_conn.load(Ordering::Relaxed);
let total = self.proxy_stats.total_conn.load(Ordering::Relaxed);
let ws = self.proxy_stats.ws_active.load(Ordering::Relaxed);
// --- Top bar ---
egui::TopBottomPanel::top("top").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.heading("TG Unblock");
ui.separator();
if running {
ui.colored_label(
egui::Color32::from_rgb(80, 220, 120),
egui::RichText::new("ПРОКСИ РАБОТАЕТ").strong(),
);
ui.separator();
ui.label(format!("Соединений: {} (WS: {}) | Всего: {}", active, ws, total));
} else {
ui.label("Прокси не запущен");
}
});
});
// --- Log panel ---
egui::TopBottomPanel::bottom("log")
.min_height(130.0)
.show(ctx, |ui| {
ui.label(egui::RichText::new("Лог").strong());
ui.separator();
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.stick_to_bottom(true)
.show(ui, |ui| {
let logs = self.log.lock().unwrap();
for e in logs.iter() {
let color = if e.is_error {
egui::Color32::from_rgb(255, 100, 100)
} else {
egui::Color32::from_rgb(170, 215, 170)
};
ui.colored_label(color, format!("[{}] {}", e.ts, e.text));
}
});
});
// --- Main panel ---
egui::CentralPanel::default().show(ctx, |ui| {
ui.add_space(15.0);
ui.vertical_centered(|ui| {
if !running {
ui.label(egui::RichText::new("Обход блокировки Telegram через WebSocket-прокси").size(15.0));
ui.add_space(5.0);
ui.label("Трафик идёт через web.telegram.org — провайдер видит обычный HTTPS");
ui.add_space(15.0);
let btn = ui.add_sized(
[340.0, 55.0],
egui::Button::new(egui::RichText::new("Запустить обход").size(20.0).strong()),
);
if btn.clicked() {
self.start_proxy();
}
} else {
ui.colored_label(
egui::Color32::from_rgb(80, 220, 120),
egui::RichText::new("Обход работает").size(22.0).strong(),
);
ui.add_space(5.0);
ui.label(format!("SOCKS5 прокси на 127.0.0.1:{}", PROXY_PORT));
ui.label(format!("WebSocket-туннелей: {} | Соединений: {}", ws, active));
ui.add_space(12.0);
// Stop button
let stop = ui.add_sized(
[340.0, 42.0],
egui::Button::new(egui::RichText::new("Остановить").size(17.0)),
);
if stop.clicked() {
self.stop_proxy();
}
}
});
ui.add_space(20.0);
ui.separator();
ui.add_space(8.0);
// --- Telegram setup ---
ui.heading("Настройка Telegram Desktop");
ui.add_space(6.0);
if running {
ui.horizontal(|ui| {
if ui.button(" Настроить автоматически ").clicked() {
self.open_tg_proxy_link();
}
ui.label("(откроет Telegram, нажмите \"Подключить\")");
});
ui.add_space(8.0);
}
ui.label("Или вручную: Настройки → Продвинутые → Тип соединения → SOCKS5");
ui.add_space(4.0);
egui::Grid::new("manual_setup")
.num_columns(2)
.spacing([15.0, 4.0])
.show(ui, |ui| {
ui.label("Сервер:");
ui.monospace("127.0.0.1");
ui.end_row();
ui.label("Порт:");
ui.monospace(format!("{}", PROXY_PORT));
ui.end_row();
ui.label("Логин/Пароль:");
ui.label("оставить пустыми");
ui.end_row();
});
ui.add_space(15.0);
ui.separator();
ui.add_space(5.0);
// --- How it works ---
ui.heading("Как это работает");
ui.add_space(4.0);
ui.label("1. Локальный SOCKS5-прокси принимает соединения от Telegram");
ui.label("2. Трафик к серверам Telegram заворачивается в WebSocket (WSS)");
ui.label("3. Подключение идёт через web.telegram.org — обычный HTTPS");
ui.label("4. Провайдер/DPI не видит MTProto, не может замедлить");
ui.add_space(4.0);
ui.colored_label(
egui::Color32::from_rgb(170, 170, 170),
"Не-Telegram трафик проходит напрямую без изменений",
);
ui.add_space(12.0);
ui.separator();
ui.add_space(6.0);
// --- VPN ad ---
egui::Frame::new()
.fill(egui::Color32::from_rgb(30, 35, 50))
.corner_radius(10.0)
.inner_margin(14.0)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.colored_label(
egui::Color32::from_rgb(100, 180, 255),
egui::RichText::new("by sonic VPN").size(16.0).strong(),
);
ui.label(egui::RichText::new("").size(14.0));
ui.label(
egui::RichText::new("Полный обход блокировок для всех приложений")
.size(13.0),
);
});
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label("Быстрый VPN без ограничений скорости:");
let link = ui.add(
egui::Hyperlink::from_label_and_url(
egui::RichText::new("@bysonicvpn_bot")
.size(14.0)
.strong()
.color(egui::Color32::from_rgb(100, 200, 255)),
"https://t.me/bysonicvpn_bot",
),
);
if link.clicked() {
let _ = open::that("https://t.me/bysonicvpn_bot");
}
});
});
});
}
}

176
src/network.rs Normal file
View File

@ -0,0 +1,176 @@
use std::net::{TcpStream, SocketAddr};
use std::process::Command;
use std::time::{Duration, Instant};
pub fn detect_adapter() -> Option<String> {
let output = Command::new("powershell")
.args([
"-Command",
"(Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1).Name",
])
.output()
.ok()?;
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
if name.is_empty() {
None
} else {
Some(name)
}
}
pub fn get_current_dns() -> Option<String> {
let output = Command::new("powershell")
.args([
"-Command",
"Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object {$_.ServerAddresses.Count -gt 0} | Select-Object -First 1 -ExpandProperty ServerAddresses | Out-String",
])
.output()
.ok()?;
let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
if result.is_empty() {
Some("Не определено".to_string())
} else {
Some(result.replace('\n', ", ").replace('\r', ""))
}
}
pub fn ping_host(ip: &str) -> (bool, Option<u64>) {
let start = Instant::now();
let output = Command::new("ping")
.args(["-n", "1", "-w", "3000", ip])
.output();
match output {
Ok(out) => {
let elapsed = start.elapsed().as_millis() as u64;
let stdout = String::from_utf8_lossy(&out.stdout);
let ok = out.status.success() && (stdout.contains("TTL=") || stdout.contains("ttl="));
if ok {
// Try to extract actual time from ping output
if let Some(time_str) = extract_ping_time(&stdout) {
(true, Some(time_str))
} else {
(true, Some(elapsed))
}
} else {
(false, None)
}
}
Err(_) => (false, None),
}
}
fn extract_ping_time(output: &str) -> Option<u64> {
// Match patterns like "time=46ms" or "time<1ms" or "время=46мс"
for line in output.lines() {
let lower = line.to_lowercase();
if let Some(pos) = lower.find("time=").or_else(|| lower.find("time<")) {
let after = &lower[pos + 5..];
let num: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
if let Ok(ms) = num.parse::<u64>() {
return Some(ms);
}
}
// Russian locale
if let Some(pos) = lower.find("=").filter(|_| lower.contains("ms") || lower.contains("мс")) {
let after = &lower[pos + 1..];
let num: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
if let Ok(ms) = num.parse::<u64>() {
if ms < 10000 {
return Some(ms);
}
}
}
}
None
}
pub fn tcp_check(ip: &str, port: u16) -> (bool, Option<u64>) {
let addr: SocketAddr = format!("{}:{}", ip, port).parse().unwrap();
let start = Instant::now();
match TcpStream::connect_timeout(&addr, Duration::from_secs(5)) {
Ok(_stream) => {
let elapsed = start.elapsed().as_millis() as u64;
(true, Some(elapsed))
}
Err(_) => (false, None),
}
}
pub fn https_check(url: &str) -> (bool, Option<u64>) {
let start = Instant::now();
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.danger_accept_invalid_certs(true)
.build();
match client {
Ok(c) => match c.get(url).send() {
Ok(resp) => {
let elapsed = start.elapsed().as_millis() as u64;
(resp.status().is_success(), Some(elapsed))
}
Err(_) => (false, None),
},
Err(_) => (false, None),
}
}
/// 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) {
let tcp_targets = [
("149.154.167.51", 443u16),
("149.154.175.50", 443),
("149.154.167.91", 443),
("91.108.56.100", 443),
];
let mut total_ms: u64 = 0;
let mut ok_count: u64 = 0;
let mut fail_count: u64 = 0;
// TCP checks (x2 rounds for stability)
for _ in 0..2 {
for (ip, port) in &tcp_targets {
let (ok, latency) = tcp_check(ip, *port);
if ok {
total_ms += latency.unwrap_or(5000);
ok_count += 1;
} else {
fail_count += 1;
}
}
}
// HTTPS check — the real indicator of usable speed
let https_urls = [
"https://web.telegram.org",
"https://t.me",
];
for url in &https_urls {
let (ok, latency) = https_check(url);
if ok {
// Weight HTTPS 3x heavier since it's closer to real usage
let ms = latency.unwrap_or(10000);
total_ms += ms * 3;
ok_count += 3;
} else {
fail_count += 3;
}
}
if ok_count == 0 {
return (false, u64::MAX);
}
// Penalize failures: each fail adds 2000ms to the score
let penalty = fail_count * 2000;
let avg = (total_ms + penalty) / (ok_count + fail_count);
(true, avg)
}

265
src/ws_proxy.rs Normal file
View File

@ -0,0 +1,265 @@
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_tungstenite::tungstenite;
use tungstenite::client::IntoClientRequest;
pub struct ProxyStats {
pub running: AtomicBool,
pub active_conn: AtomicU32,
pub total_conn: AtomicU32,
pub ws_active: AtomicU32,
}
impl ProxyStats {
pub fn new() -> Arc<Self> {
Arc::new(Self {
running: AtomicBool::new(false),
active_conn: AtomicU32::new(0),
total_conn: AtomicU32::new(0),
ws_active: AtomicU32::new(0),
})
}
}
pub async fn run_proxy(port: u16, stats: Arc<ProxyStats>) -> Result<(), String> {
let addr = format!("127.0.0.1:{}", port);
let listener = TcpListener::bind(&addr)
.await
.map_err(|e| format!("Не удалось занять порт {}: {}", port, e))?;
stats.running.store(true, Ordering::SeqCst);
loop {
if !stats.running.load(Ordering::SeqCst) {
break;
}
tokio::select! {
result = listener.accept() => {
if let Ok((stream, _)) = result {
let st = stats.clone();
st.active_conn.fetch_add(1, Ordering::Relaxed);
st.total_conn.fetch_add(1, Ordering::Relaxed);
tokio::spawn(async move {
let _ = handle_socks5(stream, &st).await;
st.active_conn.fetch_sub(1, Ordering::Relaxed);
});
}
}
_ = tokio::time::sleep(std::time::Duration::from_millis(200)) => {}
}
}
stats.running.store(false, Ordering::SeqCst);
Ok(())
}
/// DC name mapping from official Telegram MTProto transport docs
fn dc_ws_url(dc: u8) -> String {
let name = match dc {
1 => "pluto",
2 => "venus",
3 => "aurora",
4 => "vesta",
5 => "flora",
_ => "venus",
};
format!("wss://{}.web.telegram.org/apiws", name)
}
async fn handle_socks5(
mut stream: TcpStream,
stats: &ProxyStats,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
stream.set_nodelay(true)?;
let mut buf = [0u8; 258];
let n = stream.read(&mut buf).await?;
if n < 2 || buf[0] != 0x05 {
return Err("Not SOCKS5".into());
}
stream.write_all(&[0x05, 0x00]).await?;
let n = stream.read(&mut buf).await?;
if n < 7 || buf[0] != 0x05 || buf[1] != 0x01 {
stream.write_all(&[0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0]).await?;
return Err("Bad CONNECT".into());
}
let (dest_addr, dest_port) = parse_dest(&buf[3..n])?;
let dc = dest_addr
.parse::<Ipv4Addr>()
.ok()
.and_then(telegram_dc);
if let Some(dc_id) = dc {
stream
.write_all(&[0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x04, 0x38])
.await?;
stats.ws_active.fetch_add(1, Ordering::Relaxed);
let result = relay_via_ws(stream, dc_id).await;
stats.ws_active.fetch_sub(1, Ordering::Relaxed);
if let Err(e) = result {
return Err(format!("WS tunnel DC{}: {}", dc_id, e).into());
}
} else {
let target = format!("{}:{}", dest_addr, dest_port);
match TcpStream::connect(&target).await {
Ok(remote) => {
let _ = remote.set_nodelay(true);
stream
.write_all(&[0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0])
.await?;
relay_tcp(stream, remote).await;
}
Err(_) => {
stream
.write_all(&[0x05, 0x05, 0x00, 0x01, 0, 0, 0, 0, 0, 0])
.await?;
}
}
}
Ok(())
}
fn parse_dest(data: &[u8]) -> Result<(String, u16), Box<dyn std::error::Error + Send + Sync>> {
match data[0] {
0x01 => {
if data.len() < 7 { return Err("short".into()); }
let ip = format!("{}.{}.{}.{}", data[1], data[2], data[3], data[4]);
let port = u16::from_be_bytes([data[5], data[6]]);
Ok((ip, port))
}
0x03 => {
let len = data[1] as usize;
if data.len() < 2 + len + 2 { return Err("short".into()); }
let domain = std::str::from_utf8(&data[2..2 + len])?.to_string();
let port = u16::from_be_bytes([data[2 + len], data[3 + len]]);
Ok((domain, port))
}
0x04 => {
if data.len() < 19 { return Err("short".into()); }
let port = u16::from_be_bytes([data[17], data[18]]);
let mut segs = [0u16; 8];
for i in 0..8 {
segs[i] = u16::from_be_bytes([data[1 + i * 2], data[2 + i * 2]]);
}
let ip = std::net::Ipv6Addr::new(
segs[0], segs[1], segs[2], segs[3], segs[4], segs[5], segs[6], segs[7],
);
Ok((ip.to_string(), port))
}
_ => Err("unknown addr type".into()),
}
}
fn telegram_dc(ip: Ipv4Addr) -> Option<u8> {
let o = ip.octets();
match (o[0], o[1]) {
(149, 154) => Some(match o[2] {
160..=163 => 1,
164..=167 => 2,
168..=171 => 3,
172..=175 => 1,
_ => 2,
}),
(91, 108) => Some(match o[2] {
56..=59 => 5,
8..=11 => 3,
12..=15 => 4,
_ => 2,
}),
(91, 105) => Some(2),
(185, 76) => Some(2),
_ => None,
}
}
async fn relay_via_ws(
tcp_stream: TcpStream,
dc_id: u8,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use futures_util::{SinkExt, StreamExt};
let ws_url = dc_ws_url(dc_id);
let mut request = ws_url.as_str().into_client_request()?;
// Required by the Telegram WebSocket transport protocol
request.headers_mut().insert(
"Sec-WebSocket-Protocol",
"binary".parse()?,
);
request.headers_mut().insert(
"Origin",
"https://web.telegram.org".parse()?,
);
let connector = tokio_tungstenite::Connector::NativeTls(
native_tls::TlsConnector::new().map_err(|e| format!("TLS: {}", e))?,
);
let (ws, _resp) = tokio_tungstenite::connect_async_tls_with_config(
request,
None,
false,
Some(connector),
)
.await?;
let (mut ws_tx, mut ws_rx) = ws.split();
let (mut tcp_rx, mut tcp_tx) = tokio::io::split(tcp_stream);
let up = async {
let mut buf = vec![0u8; 32768];
loop {
match tcp_rx.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
let msg = tungstenite::Message::Binary(buf[..n].to_vec());
if ws_tx.send(msg).await.is_err() {
break;
}
}
Err(_) => break,
}
}
let _ = ws_tx.close().await;
};
let down = async {
while let Some(Ok(msg)) = ws_rx.next().await {
match msg {
tungstenite::Message::Binary(data) => {
if tcp_tx.write_all(&data).await.is_err() {
break;
}
}
tungstenite::Message::Close(_) => break,
_ => {}
}
}
};
tokio::select! {
_ = up => {}
_ = down => {}
}
Ok(())
}
async fn relay_tcp(client: TcpStream, remote: TcpStream) {
let (mut cr, mut cw) = tokio::io::split(client);
let (mut rr, mut rw) = tokio::io::split(remote);
tokio::select! {
_ = tokio::io::copy(&mut cr, &mut rw) => {}
_ = tokio::io::copy(&mut rr, &mut cw) => {}
}
}

21
tg_blacklist.txt Normal file
View File

@ -0,0 +1,21 @@
91.108.56.0/22
91.108.4.0/22
91.108.8.0/22
91.108.16.0/22
91.108.12.0/22
91.108.20.0/22
149.154.160.0/20
185.76.151.0/24
91.105.192.0/23
core.telegram.org
web.telegram.org
desktop.telegram.org
macos.telegram.org
updates.telegram.org
venus.web.telegram.org
pluto.web.telegram.org
flora.web.telegram.org
vesta.web.telegram.org
telegram.org
t.me
telegram.me

852
tg_unblock.bat Normal file
View File

@ -0,0 +1,852 @@
@echo off
chcp 65001 >nul 2>&1
setlocal enabledelayedexpansion
:: ============================================================
:: TG Unblock — обход блокировки/замедления Telegram
:: Требуется запуск от имени администратора
:: ============================================================
:: --- Проверка прав администратора ---
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [!] Скрипт требует прав администратора.
echo Перезапускаю с повышенными правами...
powershell -Command "Start-Process -Verb RunAs -FilePath '%~f0'"
exit /b
)
set "SCRIPT_DIR=%~dp0"
set "TOOLS_DIR=%SCRIPT_DIR%tools"
set "GDPI_DIR=%TOOLS_DIR%\goodbyedpi-0.2.3rc3-2"
set "GDPI_EXE=%GDPI_DIR%\x86_64\goodbyedpi.exe"
set "BLACKLIST=%SCRIPT_DIR%tg_blacklist.txt"
set "GDPI_URL=https://github.com/ValdikSS/GoodbyeDPI/releases/download/0.2.3rc3/goodbyedpi-0.2.3rc3-2.zip"
set "GDPI_ZIP=%TOOLS_DIR%\goodbyedpi.zip"
title TG Unblock — Обход блокировки Telegram
:MENU
cls
echo ============================================================
echo TG Unblock — Обход блокировки Telegram
echo ============================================================
echo.
echo [*] ЗАПУСТИТЬ ОБХОД (авто) — нажмите 7
echo.
echo [1] Сменить DNS (Cloudflare / Google)
echo [2] Запустить GoodbyeDPI (обход DPI)
echo [3] Комбинированный режим (DNS + GoodbyeDPI)
echo [4] Тест соединения с Telegram
echo [5] Сбросить настройки (вернуть DNS, остановить GoodbyeDPI)
echo [6] Показать текущие настройки сети
echo [7] === АВТО-ОБХОД (одна кнопка) ===
echo [0] Выход
echo.
echo ============================================================
set /p "choice=Выберите действие [0-7]: "
if "%choice%"=="1" goto DNS_MENU
if "%choice%"=="2" goto DPI_START
if "%choice%"=="3" goto COMBINED
if "%choice%"=="4" goto TEST
if "%choice%"=="5" goto RESET
if "%choice%"=="6" goto SHOW_NET
if "%choice%"=="7" goto AUTO_BYPASS
if "%choice%"=="0" goto EXIT
echo [!] Неверный выбор.
timeout /t 2 >nul
goto MENU
:: ============================================================
:: 1. СМЕНА DNS
:: ============================================================
:DNS_MENU
cls
echo ============================================================
echo Смена DNS
echo ============================================================
echo.
echo [1] Cloudflare DNS (1.1.1.1 / 1.0.0.1)
echo [2] Google DNS (8.8.8.8 / 8.8.4.4)
echo [3] Quad9 DNS (9.9.9.9 / 149.112.112.112)
echo [4] Cloudflare DoH (1.1.1.1 + DNS-over-HTTPS)
echo [0] Назад
echo.
set /p "dns_choice=Выберите DNS провайдера [0-4]: "
if "%dns_choice%"=="1" (
set "DNS1=1.1.1.1"
set "DNS2=1.0.0.1"
set "DNS_NAME=Cloudflare"
)
if "%dns_choice%"=="2" (
set "DNS1=8.8.8.8"
set "DNS2=8.8.4.4"
set "DNS_NAME=Google"
)
if "%dns_choice%"=="3" (
set "DNS1=9.9.9.9"
set "DNS2=149.112.112.112"
set "DNS_NAME=Quad9"
)
if "%dns_choice%"=="4" (
set "DNS1=1.1.1.1"
set "DNS2=1.0.0.1"
set "DNS_NAME=Cloudflare DoH"
goto SET_DNS_DOH
)
if "%dns_choice%"=="0" goto MENU
if not defined DNS1 (
echo [!] Неверный выбор.
timeout /t 2 >nul
goto DNS_MENU
)
:SET_DNS
echo.
echo [*] Определяю активный сетевой адаптер...
for /f "tokens=1,2,3,4,*" %%a in ('netsh interface ipv4 show interfaces ^| findstr /i "connected"') do (
set "ADAPTER_NAME=%%e"
)
if not defined ADAPTER_NAME (
for /f "tokens=*" %%a in ('powershell -Command "(Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1).Name"') do (
set "ADAPTER_NAME=%%a"
)
)
if not defined ADAPTER_NAME (
echo [!] Не удалось определить сетевой адаптер.
echo Введите имя адаптера вручную (например: Ethernet, Wi-Fi):
set /p "ADAPTER_NAME="
)
echo [*] Адаптер: !ADAPTER_NAME!
echo [*] Устанавливаю DNS: %DNS_NAME% (%DNS1%, %DNS2%)...
netsh interface ipv4 set dnsservers "!ADAPTER_NAME!" static %DNS1% primary validate=no >nul 2>&1
netsh interface ipv4 add dnsservers "!ADAPTER_NAME!" %DNS2% index=2 validate=no >nul 2>&1
echo [*] Очищаю DNS-кеш...
ipconfig /flushdns >nul 2>&1
echo.
echo [OK] DNS успешно изменён на %DNS_NAME% (%DNS1%, %DNS2%)
echo.
pause
goto MENU
:SET_DNS_DOH
echo.
echo [*] Определяю активный сетевой адаптер...
for /f "tokens=*" %%a in ('powershell -Command "(Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1).Name"') do (
set "ADAPTER_NAME=%%a"
)
if not defined ADAPTER_NAME (
echo [!] Не удалось определить адаптер. Введите имя вручную:
set /p "ADAPTER_NAME="
)
echo [*] Адаптер: !ADAPTER_NAME!
echo [*] Устанавливаю DNS: Cloudflare (1.1.1.1, 1.0.0.1)...
netsh interface ipv4 set dnsservers "!ADAPTER_NAME!" static 1.1.1.1 primary validate=no >nul 2>&1
netsh interface ipv4 add dnsservers "!ADAPTER_NAME!" 1.0.0.1 index=2 validate=no >nul 2>&1
ipconfig /flushdns >nul 2>&1
echo [*] Включаю DNS-over-HTTPS в системе (Windows 11+)...
powershell -Command "try { Set-DnsClientDohServerAddress -ServerAddress '1.1.1.1' -DohTemplate 'https://cloudflare-dns.com/dns-query' -AllowFallbackToUdp $true -AutoUpgrade $true -ErrorAction Stop; Set-DnsClientDohServerAddress -ServerAddress '1.0.0.1' -DohTemplate 'https://cloudflare-dns.com/dns-query' -AllowFallbackToUdp $true -AutoUpgrade $true -ErrorAction Stop; Write-Host '[OK] DoH активирован' } catch { Write-Host '[!] DoH недоступен на этой версии Windows, DNS установлен без DoH' }"
echo.
echo [OK] DNS установлен на Cloudflare с DoH.
echo.
pause
goto MENU
:: ============================================================
:: 2. ЗАПУСК GoodbyeDPI
:: ============================================================
:DPI_START
cls
echo ============================================================
echo GoodbyeDPI — обход DPI
echo ============================================================
echo.
:: Проверяем наличие GoodbyeDPI
if not exist "%GDPI_EXE%" (
echo [!] GoodbyeDPI не найден.
goto DOWNLOAD_GDPI
)
:DPI_MODE_MENU
echo.
echo Выберите режим GoodbyeDPI:
echo.
echo [1] Режим -9 (максимальная защита, рекомендуется)
echo [2] Режим -5 (средний)
echo [3] Режим -1 (базовый, совместимый)
echo [4] Кастомный (-e 2 -f 2 -q -r -m)
echo [5] Авто-перебор (попробует все режимы)
echo [0] Назад
echo.
set /p "dpi_mode=Выберите режим [0-5]: "
if "%dpi_mode%"=="0" goto MENU
:: Убиваем старый процесс если запущен
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
if "%dpi_mode%"=="1" (
echo [*] Запускаю GoodbyeDPI в режиме -9 (максимальная защита)...
start "" /B "%GDPI_EXE%" -9 --blacklist "%BLACKLIST%"
goto DPI_STARTED
)
if "%dpi_mode%"=="2" (
echo [*] Запускаю GoodbyeDPI в режиме -5 (средний)...
start "" /B "%GDPI_EXE%" -5 --blacklist "%BLACKLIST%"
goto DPI_STARTED
)
if "%dpi_mode%"=="3" (
echo [*] Запускаю GoodbyeDPI в режиме -1 (базовый)...
start "" /B "%GDPI_EXE%" -1 --blacklist "%BLACKLIST%"
goto DPI_STARTED
)
if "%dpi_mode%"=="4" (
echo [*] Запускаю GoodbyeDPI в кастомном режиме...
start "" /B "%GDPI_EXE%" -e 2 -f 2 -q -r -m --blacklist "%BLACKLIST%"
goto DPI_STARTED
)
if "%dpi_mode%"=="5" goto DPI_AUTO
echo [!] Неверный выбор.
timeout /t 2 >nul
goto DPI_MODE_MENU
:DPI_STARTED
timeout /t 2 >nul
tasklist /fi "imagename eq goodbyedpi.exe" 2>nul | findstr /i "goodbyedpi" >nul
if %errorlevel%==0 (
echo.
echo [OK] GoodbyeDPI запущен успешно!
echo Работает в фоне. Для остановки используйте пункт 5 (Сброс).
) else (
echo.
echo [!] GoodbyeDPI не удалось запустить.
echo Проверьте что антивирус не блокирует WinDivert.
)
echo.
pause
goto MENU
:: ============================================================
:: АВТО-ПЕРЕБОР РЕЖИМОВ
:: ============================================================
:DPI_AUTO
echo.
echo [*] Авто-перебор режимов GoodbyeDPI...
echo [*] Буду пробовать каждый режим и тестировать соединение.
echo.
set "MODES=-9;-5;-1;-e 2 -f 2 -q -r -m;-e 1 -f 1 -q -r -m -s;-p -f 2 -e 2 -q"
set "MODE_NAMES=Режим -9 (макс);Режим -5 (средний);Режим -1 (базовый);Кастом1 (-e2 -f2 -q -r -m);Кастом2 (-e1 -f1 -q -r -m -s);Кастом3 (-p -f2 -e2 -q)"
set "mode_idx=0"
set "best_mode="
for %%m in ("-9" "-5" "-1") do (
set /a mode_idx+=1
echo --- Тест !mode_idx!: GoodbyeDPI %%~m ---
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" %%~m --blacklist "%BLACKLIST%"
timeout /t 3 >nul
tasklist /fi "imagename eq goodbyedpi.exe" 2>nul | findstr /i "goodbyedpi" >nul
if !errorlevel! neq 0 (
echo [FAIL] Не запустился
) else (
powershell -Command "$r = try { (Invoke-WebRequest -Uri 'https://web.telegram.org' -TimeoutSec 10 -UseBasicParsing).StatusCode } catch { 0 }; if ($r -eq 200) { Write-Host ' [OK] web.telegram.org доступен!'; exit 0 } else { Write-Host ' [FAIL] web.telegram.org недоступен'; exit 1 }"
if !errorlevel!==0 (
set "best_mode=%%~m"
echo [***] Рабочий режим найден: %%~m
)
)
)
:: Пробуем кастомные режимы
for %%p in (
"-e 2 -f 2 -q -r -m"
"-e 1 -f 1 -q -r -m -s"
"-p -f 2 -e 2 -q"
) do (
set /a mode_idx+=1
echo --- Тест !mode_idx!: GoodbyeDPI %%~p ---
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" %%~p --blacklist "%BLACKLIST%"
timeout /t 3 >nul
tasklist /fi "imagename eq goodbyedpi.exe" 2>nul | findstr /i "goodbyedpi" >nul
if !errorlevel! neq 0 (
echo [FAIL] Не запустился
) else (
powershell -Command "$r = try { (Invoke-WebRequest -Uri 'https://web.telegram.org' -TimeoutSec 10 -UseBasicParsing).StatusCode } catch { 0 }; if ($r -eq 200) { Write-Host ' [OK] web.telegram.org доступен!'; exit 0 } else { Write-Host ' [FAIL] web.telegram.org недоступен'; exit 1 }"
if !errorlevel!==0 (
if not defined best_mode (
set "best_mode=%%~p"
echo [***] Рабочий режим найден: %%~p
)
)
)
)
echo.
if defined best_mode (
echo ============================================================
echo [OK] Лучший рабочий режим: !best_mode!
echo Перезапускаю GoodbyeDPI с этим режимом...
echo ============================================================
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" !best_mode! --blacklist "%BLACKLIST%"
) else (
echo ============================================================
echo [!] Ни один режим GoodbyeDPI не помог.
echo Возможно, нужен VPN или MTProxy.
echo ============================================================
taskkill /f /im goodbyedpi.exe >nul 2>&1
)
echo.
pause
goto MENU
:: ============================================================
:: 3. КОМБИНИРОВАННЫЙ РЕЖИМ
:: ============================================================
:COMBINED
cls
echo ============================================================
echo Комбинированный режим (DNS + GoodbyeDPI)
echo ============================================================
echo.
:: --- DNS ---
echo [*] Устанавливаю DNS Cloudflare (1.1.1.1)...
for /f "tokens=*" %%a in ('powershell -Command "(Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1).Name"') do (
set "ADAPTER_NAME=%%a"
)
if defined ADAPTER_NAME (
netsh interface ipv4 set dnsservers "!ADAPTER_NAME!" static 1.1.1.1 primary validate=no >nul 2>&1
netsh interface ipv4 add dnsservers "!ADAPTER_NAME!" 1.0.0.1 index=2 validate=no >nul 2>&1
ipconfig /flushdns >nul 2>&1
echo [OK] DNS установлен: Cloudflare (1.1.1.1, 1.0.0.1)
) else (
echo [!] Не удалось определить сетевой адаптер для DNS.
)
:: --- GoodbyeDPI ---
if not exist "%GDPI_EXE%" (
echo.
echo [!] GoodbyeDPI не найден. Скачиваю...
call :DO_DOWNLOAD_GDPI
if not exist "%GDPI_EXE%" (
echo [!] Не удалось скачать GoodbyeDPI. Работаю только с DNS.
pause
goto MENU
)
)
echo.
echo [*] Запускаю GoodbyeDPI в режиме -9...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -9 --blacklist "%BLACKLIST%"
timeout /t 2 >nul
tasklist /fi "imagename eq goodbyedpi.exe" 2>nul | findstr /i "goodbyedpi" >nul
if %errorlevel%==0 (
echo [OK] GoodbyeDPI запущен.
) else (
echo [!] GoodbyeDPI не запустился. Пробую режим -5...
start "" /B "%GDPI_EXE%" -5 --blacklist "%BLACKLIST%"
timeout /t 2 >nul
)
echo.
echo ============================================================
echo [OK] Комбинированный режим активирован:
echo DNS: Cloudflare 1.1.1.1
echo DPI: GoodbyeDPI
echo ============================================================
echo.
:: Быстрый тест
echo [*] Проверяю доступность Telegram...
powershell -Command "$r = try { $sw = [System.Diagnostics.Stopwatch]::StartNew(); $resp = Invoke-WebRequest -Uri 'https://web.telegram.org' -TimeoutSec 15 -UseBasicParsing; $sw.Stop(); Write-Host \"[OK] web.telegram.org доступен (${($sw.ElapsedMilliseconds)}ms)\"; } catch { Write-Host '[!] web.telegram.org пока недоступен, подождите немного...' }"
echo.
pause
goto MENU
:: ============================================================
:: 4. ТЕСТ СОЕДИНЕНИЯ
:: ============================================================
:TEST
cls
echo ============================================================
echo Тест соединения с Telegram
echo ============================================================
echo.
echo [*] Проверяю GoodbyeDPI...
tasklist /fi "imagename eq goodbyedpi.exe" 2>nul | findstr /i "goodbyedpi" >nul
if %errorlevel%==0 (
echo GoodbyeDPI: ЗАПУЩЕН
) else (
echo GoodbyeDPI: не запущен
)
echo.
echo [*] Текущий DNS:
powershell -Command "Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object {$_.ServerAddresses.Count -gt 0} | Format-Table InterfaceAlias, ServerAddresses -AutoSize"
echo.
echo [*] Пинг серверов Telegram (DC1-DC5)...
echo.
set "TG_IPS=149.154.175.50 149.154.167.51 149.154.175.100 149.154.167.91 91.108.56.100 91.108.4.100"
for %%i in (%TG_IPS%) do (
echo Пинг %%i...
ping -n 1 -w 3000 %%i >nul 2>&1
if !errorlevel!==0 (
for /f "tokens=*" %%t in ('ping -n 1 -w 3000 %%i ^| findstr /i "time="') do (
echo [OK] %%i — %%t
)
) else (
echo [FAIL] %%i — недоступен
)
)
echo.
echo [*] Проверяю TCP-соединение (порт 443)...
echo.
for %%i in (149.154.175.50 149.154.167.51 91.108.56.100) do (
powershell -Command "$tcp = New-Object System.Net.Sockets.TcpClient; try { $tcp.ConnectAsync('%%i', 443).Wait(5000) | Out-Null; if ($tcp.Connected) { Write-Host ' [OK] %%i:443 — TCP доступен' } else { Write-Host ' [FAIL] %%i:443 — таймаут' } } catch { Write-Host ' [FAIL] %%i:443 — отказано' } finally { $tcp.Dispose() }"
)
echo.
echo [*] Проверяю HTTPS доступность...
echo.
for %%u in (
"https://web.telegram.org"
"https://core.telegram.org"
"https://t.me"
) do (
powershell -Command "$url = '%%~u'; try { $sw = [System.Diagnostics.Stopwatch]::StartNew(); $r = Invoke-WebRequest -Uri $url -TimeoutSec 10 -UseBasicParsing; $sw.Stop(); $ms = $sw.ElapsedMilliseconds; Write-Host \" [OK] $url — HTTP $($r.StatusCode) (${ms}ms)\" } catch { Write-Host \" [FAIL] $url — недоступен\" }"
)
echo.
echo [*] Проверяю DNS резолвинг...
echo.
for %%d in (web.telegram.org core.telegram.org t.me telegram.org) do (
powershell -Command "try { $ip = [System.Net.Dns]::GetHostAddresses('%%d') | Select-Object -First 1; Write-Host \" [OK] %%d -> $($ip.IPAddressToString)\" } catch { Write-Host ' [FAIL] %%d — DNS не резолвится' }"
)
echo.
echo ============================================================
echo Тест завершён
echo ============================================================
echo.
pause
goto MENU
:: ============================================================
:: 5. СБРОС НАСТРОЕК
:: ============================================================
:RESET
cls
echo ============================================================
echo Сброс настроек
echo ============================================================
echo.
echo [*] Останавливаю GoodbyeDPI...
taskkill /f /im goodbyedpi.exe >nul 2>&1
echo [OK] GoodbyeDPI остановлен.
echo.
echo [*] Возвращаю DNS к автоматическому (DHCP)...
for /f "tokens=*" %%a in ('powershell -Command "(Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1).Name"') do (
set "ADAPTER_NAME=%%a"
)
if defined ADAPTER_NAME (
netsh interface ipv4 set dnsservers "!ADAPTER_NAME!" dhcp >nul 2>&1
ipconfig /flushdns >nul 2>&1
echo [OK] DNS сброшен на DHCP (автоматический).
) else (
echo [!] Не удалось определить адаптер. Сбросьте DNS вручную.
)
echo.
echo [OK] Все настройки сброшены.
echo.
pause
goto MENU
:: ============================================================
:: 6. ПОКАЗАТЬ ТЕКУЩИЕ НАСТРОЙКИ
:: ============================================================
:SHOW_NET
cls
echo ============================================================
echo Текущие сетевые настройки
echo ============================================================
echo.
echo [*] Сетевые адаптеры:
powershell -Command "Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Format-Table Name, InterfaceDescription, Status, LinkSpeed -AutoSize"
echo [*] DNS серверы:
powershell -Command "Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object {$_.ServerAddresses.Count -gt 0} | Format-Table InterfaceAlias, ServerAddresses -AutoSize"
echo [*] IP-конфигурация:
ipconfig | findstr /i "IPv4 Subnet Gateway DNS"
echo.
echo [*] GoodbyeDPI:
tasklist /fi "imagename eq goodbyedpi.exe" 2>nul | findstr /i "goodbyedpi" >nul
if %errorlevel%==0 (
echo Статус: ЗАПУЩЕН
for /f "tokens=2" %%p in ('tasklist /fi "imagename eq goodbyedpi.exe" /fo list ^| findstr "PID"') do (
echo PID: %%p
)
) else (
echo Статус: не запущен
)
echo.
pause
goto MENU
:: ============================================================
:: СКАЧИВАНИЕ GoodbyeDPI
:: ============================================================
:DOWNLOAD_GDPI
echo.
echo [*] GoodbyeDPI необходим для обхода DPI.
echo Скачать автоматически с GitHub?
echo.
echo [1] Да, скачать
echo [0] Нет, назад
echo.
set /p "dl_choice=Выбор [0-1]: "
if "%dl_choice%"=="0" goto MENU
if "%dl_choice%"=="1" (
call :DO_DOWNLOAD_GDPI
if exist "%GDPI_EXE%" goto DPI_MODE_MENU
pause
goto MENU
)
goto MENU
:DO_DOWNLOAD_GDPI
echo.
echo [*] Создаю папку tools...
if not exist "%TOOLS_DIR%" mkdir "%TOOLS_DIR%"
echo [*] Скачиваю GoodbyeDPI с GitHub...
echo URL: %GDPI_URL%
echo.
powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; try { Invoke-WebRequest -Uri '%GDPI_URL%' -OutFile '%GDPI_ZIP%' -UseBasicParsing; Write-Host '[OK] Скачано успешно' } catch { Write-Host \"[!] Ошибка скачивания: $($_.Exception.Message)\"; exit 1 }"
if not exist "%GDPI_ZIP%" (
echo [!] Не удалось скачать файл.
echo Попробуйте скачать вручную:
echo %GDPI_URL%
echo и распаковать в %GDPI_DIR%
exit /b 1
)
echo [*] Распаковываю...
powershell -Command "try { Expand-Archive -Path '%GDPI_ZIP%' -DestinationPath '%TOOLS_DIR%' -Force; Write-Host '[OK] Распаковано' } catch { Write-Host \"[!] Ошибка распаковки: $($_.Exception.Message)\"; exit 1 }"
:: Ищем goodbyedpi.exe рекурсивно
if not exist "%GDPI_EXE%" (
echo [*] Ищу goodbyedpi.exe...
for /f "tokens=*" %%f in ('powershell -Command "Get-ChildItem -Path '%TOOLS_DIR%' -Recurse -Filter 'goodbyedpi.exe' | Select-Object -First 1 | ForEach-Object { $_.DirectoryName }"') do (
set "FOUND_DIR=%%f"
)
if defined FOUND_DIR (
echo [*] Найден в: !FOUND_DIR!
set "GDPI_DIR=!FOUND_DIR!"
set "GDPI_EXE=!FOUND_DIR!\goodbyedpi.exe"
:: Обновим путь для x86_64
if not exist "!FOUND_DIR!\goodbyedpi.exe" (
for /f "tokens=*" %%f in ('powershell -Command "Get-ChildItem -Path '%TOOLS_DIR%' -Recurse -Filter 'goodbyedpi.exe' | Select-Object -First 1 | ForEach-Object { $_.FullName }"') do (
set "GDPI_EXE=%%f"
echo [*] Путь к exe: %%f
)
)
)
)
if exist "%GDPI_EXE%" (
echo.
echo [OK] GoodbyeDPI установлен: %GDPI_EXE%
del "%GDPI_ZIP%" >nul 2>&1
) else (
echo.
echo [!] goodbyedpi.exe не найден после распаковки.
echo Проверьте папку: %TOOLS_DIR%
)
exit /b 0
:: ============================================================
:: 7. АВТО-ОБХОД (одна кнопка) — замеряет ВСЕ режимы,
:: выбирает самый быстрый
:: ============================================================
:AUTO_BYPASS
cls
echo ============================================================
echo АВТО-ОБХОД — тестирую ВСЕ режимы, выбираю лучший...
echo Это займёт 2-3 минуты.
echo ============================================================
echo.
:: --- Шаг 1: Определяю адаптер ---
echo [1] Определяю сетевой адаптер...
set "ADAPTER_NAME="
for /f "tokens=*" %%a in ('powershell -Command "(Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1).Name"') do (
set "ADAPTER_NAME=%%a"
)
if not defined ADAPTER_NAME (
echo [!] Не удалось определить адаптер.
pause
goto MENU
)
echo Адаптер: !ADAPTER_NAME!
:: --- Шаг 2: DNS ---
echo.
echo [2] Устанавливаю DNS Cloudflare (1.1.1.1)...
netsh interface ipv4 set dnsservers "!ADAPTER_NAME!" static 1.1.1.1 primary validate=no >nul 2>&1
netsh interface ipv4 add dnsservers "!ADAPTER_NAME!" 1.0.0.1 index=2 validate=no >nul 2>&1
ipconfig /flushdns >nul 2>&1
echo OK
:: --- Шаг 3: GoodbyeDPI ---
echo.
echo [3] Ищу GoodbyeDPI...
if not exist "%GDPI_EXE%" (
echo Не найден, скачиваю...
call :DO_DOWNLOAD_GDPI
if not exist "%GDPI_EXE%" (
echo [!] Не удалось скачать GoodbyeDPI.
pause
goto MENU
)
)
echo OK: %GDPI_EXE%
:: --- Шаг 4: Бенчмарк каждого режима ---
echo.
echo ============================================================
echo Замеряю скорость каждого режима...
echo ============================================================
echo.
set "BEST_MODE="
set "BEST_SCORE=99999"
set "BEST_LABEL="
set "MODE_NUM=0"
:: Benchmark function is a powershell one-liner that does
:: 2x TCP + 1x HTTPS, returns average ms (lower=better), 99999 if fail
set "BENCH_CMD=powershell -Command "$total=0; $ok=0; foreach($ip in @('149.154.167.51','149.154.175.50','91.108.56.100')) { foreach($i in 1..2) { $tcp=New-Object Net.Sockets.TcpClient; $sw=[Diagnostics.Stopwatch]::StartNew(); try { [void]$tcp.ConnectAsync($ip,443).Wait(4000); if($tcp.Connected){$sw.Stop();$total+=$sw.ElapsedMilliseconds;$ok++} } catch {} finally {$tcp.Dispose()} } }; try { $sw2=[Diagnostics.Stopwatch]::StartNew(); $r=Invoke-WebRequest 'https://web.telegram.org' -TimeoutSec 8 -UseBasicParsing; $sw2.Stop(); $total+=$sw2.ElapsedMilliseconds*3; $ok+=3 } catch {}; if($ok -eq 0){Write-Host 99999}else{Write-Host([math]::Floor($total/$ok))}""
:: Mode 1: без GoodbyeDPI (только DNS)
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] Только DNS (без GoodbyeDPI)...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=dns_only"
set "BEST_LABEL=Только DNS"
)
:: Mode 2: -9
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -9 (максимальная защита)...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -9 --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-9"
set "BEST_LABEL=GoodbyeDPI -9"
)
:: Mode 3: -5
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -5 (средний)...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -5 --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-5"
set "BEST_LABEL=GoodbyeDPI -5"
)
:: Mode 4: -1
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -1 (базовый)...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -1 --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-1"
set "BEST_LABEL=GoodbyeDPI -1"
)
:: Mode 5: custom -e2 -f2 -q -r -m
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -e2 -f2 -q -r -m...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -e 2 -f 2 -q -r -m --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-e 2 -f 2 -q -r -m"
set "BEST_LABEL=GoodbyeDPI -e2 -f2 -q -r -m"
)
:: Mode 6: custom -p -f2 -e2 -q
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -p -f2 -e2 -q...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -p -f 2 -e 2 -q --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-p -f 2 -e 2 -q"
set "BEST_LABEL=GoodbyeDPI -p -f2 -e2 -q"
)
:: Mode 7: custom -e40 -f2 -q -r -m
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -e40 -f2 -q -r -m...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -e 40 -f 2 -q -r -m --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-e 40 -f 2 -q -r -m"
set "BEST_LABEL=GoodbyeDPI -e40 -f2 -q -r -m"
)
:: Mode 8: -9 --set-ttl 5
set /a MODE_NUM+=1
echo [%MODE_NUM%/8] GoodbyeDPI -9 --set-ttl 5...
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
start "" /B "%GDPI_EXE%" -9 --set-ttl 5 --blacklist "%BLACKLIST%"
timeout /t 2 >nul
for /f "tokens=*" %%s in ('%BENCH_CMD%') do set "SCORE=%%s"
echo Скорость: !SCORE!ms
if !SCORE! LSS !BEST_SCORE! (
set "BEST_SCORE=!SCORE!"
set "BEST_MODE=-9 --set-ttl 5"
set "BEST_LABEL=GoodbyeDPI -9 --set-ttl 5"
)
:: --- Применяем лучший ---
echo.
echo ============================================================
if "!BEST_SCORE!"=="99999" (
echo [!] Ни один режим не сработал. Попробуйте VPN или MTProxy.
taskkill /f /im goodbyedpi.exe >nul 2>&1
pause
goto MENU
)
echo ЛУЧШИЙ РЕЖИМ: !BEST_LABEL!
echo СКОРОСТЬ: !BEST_SCORE!ms
echo ============================================================
echo.
taskkill /f /im goodbyedpi.exe >nul 2>&1
timeout /t 1 >nul
if "!BEST_MODE!"=="dns_only" (
echo [OK] DNS Cloudflare достаточно, GoodbyeDPI не нужен.
) else (
echo [*] Запускаю GoodbyeDPI с лучшими параметрами...
start "" /B "%GDPI_EXE%" !BEST_MODE! --blacklist "%BLACKLIST%"
timeout /t 2 >nul
echo [OK] GoodbyeDPI запущен: !BEST_MODE!
)
echo.
echo ============================================================
echo [OK] ОБХОД АКТИВИРОВАН!
echo DNS: Cloudflare 1.1.1.1
echo Режим: !BEST_LABEL! (!BEST_SCORE!ms)
echo Для остановки — пункт 5 в меню.
echo ============================================================
pause
goto MENU
:: ============================================================
:: ВЫХОД
:: ============================================================
:EXIT
echo.
echo [*] GoodbyeDPI будет остановлен при выходе? (y/n)
set /p "exit_choice="
if /i "%exit_choice%"=="y" (
taskkill /f /im goodbyedpi.exe >nul 2>&1
echo [OK] GoodbyeDPI остановлен.
)
echo.
echo Bye!
endlocal
exit /b 0