From 09b7a03a0a3c13210ca85cc809c7e4b1813379d0 Mon Sep 17 00:00:00 2001 From: by-sonic Date: Wed, 8 Apr 2026 14:56:50 +0300 Subject: [PATCH] =?UTF-8?q?v1.0.0:=20Clean=20rewrite=20=E2=80=94=20cross-p?= =?UTF-8?q?latform,=20dark=20UI,=20stable=20WS=20tunnel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor: --- .github/workflows/release.yml | 77 +-- .gitignore | 5 +- Cargo.toml | 18 +- HABR_ARTICLE.md | 307 ------------ README.md | 191 +++----- src/bypass.rs | 201 -------- src/main.rs | 523 ++++++++++----------- src/network.rs | 176 ------- src/proxy.rs | 232 +++++++++ src/ws_proxy.rs | 306 ------------ tg_blacklist.txt | 21 - tg_unblock.bat | 852 ---------------------------------- РЕШЕНИЯ.md | 418 ----------------- 13 files changed, 594 insertions(+), 2733 deletions(-) delete mode 100644 HABR_ARTICLE.md delete mode 100644 src/bypass.rs delete mode 100644 src/network.rs create mode 100644 src/proxy.rs delete mode 100644 src/ws_proxy.rs delete mode 100644 tg_blacklist.txt delete mode 100644 tg_unblock.bat delete mode 100644 РЕШЕНИЯ.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e201400..59ef538 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,64 +1,83 @@ -name: Build & Release +name: Release on: push: - tags: - - 'v*' + tags: ['v*'] permissions: contents: write jobs: build: - runs-on: windows-latest + strategy: + matrix: + include: + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact: tglock.exe + - os: macos-latest + target: x86_64-apple-darwin + artifact: tglock-macos-x64 + - os: macos-latest + target: aarch64-apple-darwin + artifact: tglock-macos-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact: tglock-linux-x64 + + runs-on: ${{ matrix.os }} + 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') }} + targets: ${{ matrix.target }} - - name: Build release - run: cargo build --release + - name: Build + run: cargo build --release --target ${{ matrix.target }} - - name: Upload artifact + - name: Rename (Unix) + if: runner.os != 'Windows' + run: cp target/${{ matrix.target }}/release/tglock ${{ matrix.artifact }} + + - name: Rename (Windows) + if: runner.os == 'Windows' + run: copy target\${{ matrix.target }}\release\tglock.exe ${{ matrix.artifact }} + + - name: Upload uses: actions/upload-artifact@v4 with: - name: tg_unblock-windows-x64 - path: target/release/tg_unblock.exe + name: ${{ matrix.artifact }} + path: ${{ matrix.artifact }} release: needs: build runs-on: ubuntu-latest steps: - - name: Download artifact + - name: Download artifacts 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 + files: | + tglock.exe/tglock.exe + tglock-macos-x64/tglock-macos-x64 + tglock-macos-arm64/tglock-macos-arm64 + tglock-linux-x64/tglock-linux-x64 body: | - ## TG Unblock ${{ github.ref_name }} + ## TGLock ${{ github.ref_name }} - Обход блокировки Telegram через WebSocket-туннель. + Обход блокировки Telegram через WebSocket-туннель. Один клик. - ### Установка - 1. Скачайте `tg_unblock.exe` - 2. Запустите - 3. Нажмите "Запустить обход" - 4. Нажмите "Настроить автоматически" + | Файл | Платформа | + |---|---| + | `tglock.exe` | Windows x64 | + | `tglock-macos-x64` | macOS Intel | + | `tglock-macos-arm64` | macOS Apple Silicon | + | `tglock-linux-x64` | Linux x64 | - --- **[by sonic VPN](https://t.me/bysonicvpn_bot)** — полный обход блокировок для всех приложений diff --git a/.gitignore b/.gitignore index e429fd6..1a1b997 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /target /tools -*.zip -*.exe -!*.rs Cargo.lock +*.zip +.claude/ diff --git a/Cargo.toml b/Cargo.toml index aa28091..5a861a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,26 +1,22 @@ [package] -name = "tg_unblock" -version = "0.3.1" +name = "tglock" +version = "1.0.0" edition = "2021" +description = "Telegram unblock via local WebSocket tunnel" +license = "MIT" [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 = { version = "1", features = ["rt-multi-thread", "net", "io-util", "time", "macros"] } tokio-tungstenite = { version = "0.24", features = ["native-tls"] } native-tls = "0.2" futures-util = "0.3" aes = "0.8" ctr = "0.9" cipher = "0.4" - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser"] } +open = "5" [[bin]] -name = "tg_unblock" +name = "tglock" path = "src/main.rs" diff --git a/HABR_ARTICLE.md b/HABR_ARTICLE.md deleted file mode 100644 index da10f54..0000000 --- a/HABR_ARTICLE.md +++ /dev/null @@ -1,307 +0,0 @@ -# Как я написал обход блокировки Telegram на Rust — без VPN, без серверов, через WebSocket - -**Простой · 7 мин · Rust · Open source · Windows · Сетевые технологии · Из песочницы** - -**TL;DR:** Написал open-source десктопное приложение **TG Unblock** на Rust, которое в один клик обходит блокировку Telegram через локальный WebSocket-прокси. Трафик заворачивается в обычный HTTPS к `web.telegram.org` — DPI не видит MTProto, провайдер не может шейпить. Без VPN, без серверов, без абонентки. Код на GitHub — [by-sonic/tglock](https://github.com/by-sonic/tglock). - ---- - -## Предыстория: почему GoodbyeDPI не спасает - -С весны 2025 года Telegram в России стал работать, мягко говоря, через боль. Сообщения доходят по 10 секунд, медиа не грузятся, звонки рвутся. Классическая картина: провайдер + DPI = страдания. - -Первое, что приходит в голову — **GoodbyeDPI**. Запустил, пакеты фрагментируются, DPI не узнаёт MTProto... и вроде работает. Но: - -- **Пинг 200+ мс** — при норме 40–60 -- **Постоянные переподключения** — DPI переобучается и режет соединения -- **IP-шейпинг** — провайдер троттлит весь трафик к подсетям Telegram (149.154.x.x, 91.108.x.x) - -GoodbyeDPI обманывает DPI на уровне пакетов, но **не решает проблему IP-шейпинга**. Если провайдер тупо режет скорость ко всем IP Telegram — хоть как фрагментируй, будет медленно. - -VPN — вариант. Но: -- Платные стоят денег и сливают скорость -- Бесплатные сливают данные -- Не все работают стабильно -- Для одного Telegram гонять весь трафик через VPN — оверкилл - -Нужно решение, которое **маскирует сам факт подключения к Telegram**, а не просто прячет протокол. - -## Идея: WebSocket-туннель через web.telegram.org - -Я провёл серию тестов. Прямое подключение к серверам Telegram (149.154.167.51:443) — либо таймаут, либо 200+ мс. А вот `web.telegram.org` отвечает стабильно за 50–80 мс через HTTPS. Провайдер его не трогает — это же «обычный сайт». - -И тут я полез в [документацию MTProto](https://core.telegram.org/mtproto/transports) и нашёл золотую жилу: - -> **WebSocket:** Implementation of the WebSocket transport is pretty much the same as with TCP... all data received and sent through WebSocket messages is to be treated as a single duplex stream of bytes, just like with TCP. - -Telegram **официально поддерживает WebSocket-транспорт**. Серверы `pluto.web.telegram.org`, `venus.web.telegram.org` и т.д. — это не просто веб-клиент. Это **полноценные точки входа в сеть Telegram** через WSS. - -Схема: - -``` -Telegram Desktop - │ - ▼ SOCKS5 -┌──────────────────┐ -│ TG Unblock │ 127.0.0.1:1080 -│ WS-прокси │ -└──────┬───────────┘ - │ - ├── IP Telegram? ──► WSS к {dc}.web.telegram.org/apiws - │ (провайдер видит: HTTPS к web.telegram.org) - │ - └── Другой IP? ────► Прямой TCP (без изменений) -``` - -Провайдер видит: -- Соединение к `venus.web.telegram.org` по порту 443 -- Обычный TLS/HTTPS трафик -- Никакого MTProto - -DPI видит: -- Ничего подозрительного -- Обычный WebSocket внутри HTTPS - -Результат: -- **Полная скорость** — провайдер не шейпит web.telegram.org -- **Нет переподключений** — DPI не трогает HTTPS -- **Нулевая задержка** — нет промежуточных серверов, трафик идёт напрямую к Telegram - -## Реализация: Rust, SOCKS5, WebSocket - -### Почему Rust? - -Не Electron. Не Python. Не Node.js. **Rust.** Потому что: -- Один бинарник ~6 МБ, без зависимостей -- Нативная скорость — прокси не должен добавлять задержку -- Async I/O через tokio — тысячи одновременных соединений -- Компилируется, запускается, работает - -### Архитектура - -Приложение состоит из 4 модулей: - -| Модуль | Что делает | -|---|---| -| `main.rs` | GUI на egui + управление прокси | -| `ws_proxy.rs` | SOCKS5-сервер + WebSocket-туннель | -| `bypass.rs` | DNS-настройка, системные утилиты | -| `network.rs` | Сетевая диагностика | - -### SOCKS5 → WebSocket: как это работает - -Когда Telegram Desktop подключается через SOCKS5-прокси, происходит следующее: - -**1. SOCKS5 handshake** - -```rust -// Клиент: [0x05, 0x01, 0x00] — SOCKS5, 1 метод, no auth -// Сервер: [0x05, 0x00] — принято -// Клиент: [0x05, 0x01, 0x00, 0x01, IP, PORT] — CONNECT к IP:PORT -``` - -**2. Определение DC по IP** - -Telegram использует фиксированные подсети для каждого Data Center. Из [документации](https://core.telegram.org/mtproto/transports): - -```rust -fn telegram_dc(ip: Ipv4Addr) -> Option { - let o = ip.octets(); - match (o[0], o[1]) { - (149, 154) => Some(match o[2] { - 160..=163 => 1, // DC1 - 164..=167 => 2, // DC2 - 168..=171 => 3, // DC3 - 172..=175 => 1, // DC1 alt - _ => 2, - }), - (91, 108) => Some(match o[2] { - 56..=59 => 5, // DC5 - 8..=11 => 3, // DC3 - 12..=15 => 4, // DC4 - _ => 2, - }), - (91, 105) => Some(2), - (185, 76) => Some(2), - _ => None, - } -} -``` - -**3. WebSocket-туннель** - -Каждый DC имеет именованный WebSocket-эндпоинт (имена из официальной документации Telegram): - -| DC | Имя | URL | -|---|---|---| -| 1 | Pluto | `wss://pluto.web.telegram.org/apiws` | -| 2 | Venus | `wss://venus.web.telegram.org/apiws` | -| 3 | Aurora | `wss://aurora.web.telegram.org/apiws` | -| 4 | Vesta | `wss://vesta.web.telegram.org/apiws` | -| 5 | Flora | `wss://flora.web.telegram.org/apiws` | - -Обязательный заголовок (из доки Telegram): `Sec-WebSocket-Protocol: binary`. - -```rust -let mut request = ws_url.as_str().into_client_request()?; -request.headers_mut().insert( - "Sec-WebSocket-Protocol", "binary".parse()?, -); - -let (ws, _) = tokio_tungstenite::connect_async_tls_with_config( - request, None, false, Some(connector), -).await?; -``` - -**4. Двунаправленный relay** - -Ключевая цитата из документации Telegram: - -> All data received and sent through WebSocket messages is to be treated as a **single duplex stream of bytes**, just like with TCP. - -Это значит, что нам не нужно парсить MTProto. Просто relay байтов: TCP → WebSocket binary frame, WebSocket binary frame → TCP. - -```rust -let up = async { - let mut buf = vec![0u8; 32768]; - loop { - match tcp_rx.read(&mut buf).await { - Ok(0) => break, - Ok(n) => { - let msg = Message::Binary(buf[..n].to_vec()); - if ws_tx.send(msg).await.is_err() { break; } - } - Err(_) => break, - } - } -}; - -let down = async { - while let Some(Ok(msg)) = ws_rx.next().await { - if let Message::Binary(data) = msg { - if tcp_tx.write_all(&data).await.is_err() { break; } - } - } -}; - -tokio::select! { _ = up => {}, _ = down => {} } -``` - -### GUI: egui, не Electron - -Нативный GUI через `egui` / `eframe`. Никакого браузера, никакого DOM, никакого JavaScript. Вся отрисовка — immediate mode, 60 FPS. - -Кнопка «Запустить обход» делает: -1. Меняет DNS на Cloudflare (1.1.1.1) — обходит DNS-блокировку -2. Запускает SOCKS5-прокси на 127.0.0.1:1080 -3. Предлагает автонастройку Telegram через `tg://socks?server=127.0.0.1&port=1080` - -Кнопка «Настроить автоматически» — открывает Telegram Desktop с готовой конфигурацией прокси. Один клик. - -## Технические детали, которые пришлось решить - -### Проблема 1: Не-Telegram трафик - -Если Telegram Desktop пускает через SOCKS5 не только MTProto, но и запросы к CDN, стикер-серверам, обновлениям — их нельзя заворачивать в WebSocket. Решение: проверяем IP по маппингу Telegram-подсетей. Telegram IP → WebSocket. Всё остальное → прямой TCP passthrough. - -### Проблема 2: Определение DC - -Telegram Desktop использует obfuscated2 транспорт. Первые 64 байта — зашифрованный хендшейк, в котором закодирован DC ID. Парсить его — целый проект. - -Решение проще: определяем DC по destination IP. Telegram использует фиксированные подсети для каждого DC — маппинг стабильный и документированный. - -### Проблема 3: TLS к WebSocket-эндпоинтам - -WebSocket-соединение идёт через WSS (TLS). Используем `native-tls` — системные сертификаты Windows, без привязки к OpenSSL. - -```rust -let connector = tokio_tungstenite::Connector::NativeTls( - native_tls::TlsConnector::new()?, -); -``` - -### Проблема 4: Graceful shutdown - -При остановке прокси нужно: -- Сбросить DNS обратно на DHCP -- Корректно закрыть все WebSocket-соединения -- Не оставить Telegram без связи - -Используем `AtomicBool` для флага остановки — все задачи проверяют его и завершаются. - -## Сравнение с альтернативами - -| | GoodbyeDPI | Zapret | VPN | **TG Unblock** | -|---|---|---|---|---| -| Подход | Фрагментация пакетов | Desync пакетов | Туннель через сервер | WebSocket-туннель | -| DPI видит MTProto? | Нет | Нет | Нет | **Нет** | -| IP-шейпинг? | Не обходит | Не обходит | Обходит | **Обходит** | -| Нужен сервер? | Нет | Нет | Да | **Нет** | -| Скорость | Зависит от DPI | Зависит от DPI | Зависит от сервера | **Полная** | -| Весь трафик? | Нет | Нет | Да | **Только Telegram** | -| Стоимость | Бесплатно | Бесплатно | $3–10/мес | **Бесплатно** | - -## Стек - -| Технология | Зачем | -|---|---| -| **Rust** | Скорость, один бинарник, без зависимостей | -| **egui / eframe** | Нативный GUI без браузера | -| **tokio** | Async I/O, тысячи соединений | -| **tokio-tungstenite** | WebSocket-клиент с TLS | -| **native-tls** | Системные сертификаты Windows | -| **GitHub Actions** | CI/CD — автобилд при новом теге | - -## Цифры - -- **5 DC** — полный маппинг всех Telegram Data Center -- **1 бинарник** — ~6 МБ, без зависимостей -- **0 серверов** — всё работает локально -- **0₽** — полностью бесплатно и open-source -- **1 клик** — от запуска до работающего Telegram - -## Как попробовать - -### Скачать готовый .exe - -1. Скачайте `tg_unblock.exe` из [Releases](https://github.com/by-sonic/tglock/releases) -2. Запустите (желательно от администратора — для DNS) -3. Нажмите **«Запустить обход»** -4. Нажмите **«Настроить автоматически»** -5. В Telegram нажмите «Подключить» - -### Собрать из исходников - -```bash -git clone https://github.com/by-sonic/tglock.git -cd tglock -cargo build --release -# Бинарник: target/release/tg_unblock.exe -``` - -## Что дальше - -- **Автоопределение DC из obfuscated2** — парсинг первых 64 байт для точного маппинга -- **Fallback на GoodbyeDPI** — если WebSocket-эндпоинт недоступен -- **Linux / macOS** — porability через tokio + egui (уже почти готово) -- **Статистика** — скорость, задержка, количество туннелей в реальном времени - -## Вместо заключения - -Telegram — это не просто мессенджер. Для миллионов людей это рабочий инструмент, канал связи, источник информации. Когда он работает через боль — страдают все. - -GoodbyeDPI — отличный инструмент, но у него есть потолок. Когда DPI побеждён, а трафик всё равно шейпится — нужен другой подход. WebSocket-туннель через `web.telegram.org` — это как проехать мимо камеры на легальной машине вместо того, чтобы заклеивать номера. - -Код полностью открыт. Если пригодился — поставьте звезду на GitHub. Если нашли баг — PR приветствуются. - -**GitHub:** [github.com/by-sonic/tglock](https://github.com/by-sonic/tglock) - -**P.S.** Если нужен полный обход блокировок для всех приложений (YouTube, Discord, Instagram и др.) — попробуйте [by sonic VPN](https://t.me/bysonicvpn_bot). Быстрый, стабильный, без ограничений скорости. - ---- - -*by sonic* - -**Теги:** telegram, dpi bypass, websocket, rust, socks5, mtproto, обход блокировок, open-source - -**Хабы:** Rust · Open source · Windows · Сетевые технологии diff --git a/README.md b/README.md index 8cefc24..cd8b927 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,65 @@

-

TG Unblock

+

TGLock

+

Обход блокировки Telegram через WebSocket-туннель

+

Без VPN. Без серверов. Без абонентки. Один клик.

- Обход блокировки Telegram через WebSocket-туннель
- Без VPN. Без серверов. Без абонентки. Один клик. -

-

- Release - License - Stars - Rust - Windows + Release + Platform + Rust + MIT

--- -## Что это? - -**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)** +**[Последний релиз](https://github.com/by-sonic/tglock/releases/latest)** -Или собрать из исходников: +| Файл | Платформа | +|---|---| +| `tglock.exe` | Windows x64 | +| `tglock-macos-arm64` | macOS Apple Silicon (M1–M4) | +| `tglock-macos-x64` | macOS Intel | +| `tglock-linux-x64` | Linux x64 | + +## Как пользоваться + +1. Скачай и запусти +2. Нажми **ПОДКЛЮЧИТЬ** +3. Нажми **Настроить автоматически** → в Telegram нажми «Подключить» +4. Готово + +Ручная настройка: Telegram → Настройки → Продвинутые → Тип соединения → SOCKS5 → `127.0.0.1:1080` + +## Как это работает + +``` +Telegram Desktop → SOCKS5 (127.0.0.1:1080) → TGLock → WSS (web.telegram.org) → DC +``` + +1. Локальный SOCKS5-прокси перехватывает соединения Telegram +2. Из MTProto init-пакета извлекается номер DC (AES-256-CTR) +3. Трафик заворачивается в WebSocket через `kws{dc}.web.telegram.org` +4. Провайдер видит обычный HTTPS к `web.telegram.org` +5. Остальной трафик проходит напрямую + +## Почему не GoodbyeDPI / Zapret? + +GoodbyeDPI и Zapret фрагментируют пакеты чтобы обмануть DPI. Но если провайдер **шейпит по IP** — они бесполезны. + +TGLock маскирует трафик под обычный HTTPS. DPI не видит MTProto. IP-шейпинг не работает — `web.telegram.org` не блокируется. + +## Стек + +| | | +|---|---| +| Rust | Один бинарник, нативная скорость | +| egui | GUI без браузера и Electron | +| tokio | Async I/O | +| tokio-tungstenite | WebSocket + TLS | + +## Сборка ```bash git clone https://github.com/by-sonic/tglock.git @@ -42,118 +67,14 @@ cd tglock cargo build --release ``` -Готовый `.exe` будет в `target/release/tg_unblock.exe`. +## VPN -## Как пользоваться - -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)**. Быстрый, без ограничений скорости. +Для обхода блокировок **всех** приложений — **[by sonic VPN](https://t.me/bysonicvpn_bot)** ## Лицензия -MIT — делайте что хотите. - -## Автор - -**by sonic** — [@bysonicvpn_bot](https://t.me/bysonicvpn_bot) +MIT --- -

- Если пригодилось — поставьте ⭐ на GitHub -

+

by sonic

diff --git a/src/bypass.rs b/src/bypass.rs deleted file mode 100644 index dde9913..0000000 --- a/src/bypass.rs +++ /dev/null @@ -1,201 +0,0 @@ -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 { - 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, 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 { - 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 { - 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()) -} diff --git a/src/main.rs b/src/main.rs index 2f28154..5c363ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,356 +1,331 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -mod bypass; -mod network; -mod ws_proxy; +mod proxy; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; +use std::time::Instant; use eframe::egui; -const PROXY_PORT: u16 = 1080; +// -- Colors (GitHub Dark inspired) ------------------------------------------ + +const BG: egui::Color32 = egui::Color32::from_rgb(13, 17, 23); +const SURFACE: egui::Color32 = egui::Color32::from_rgb(22, 27, 34); +const BORDER: egui::Color32 = egui::Color32::from_rgb(48, 54, 61); +const ACCENT: egui::Color32 = egui::Color32::from_rgb(88, 166, 255); +const GREEN: egui::Color32 = egui::Color32::from_rgb(63, 185, 80); +const RED: egui::Color32 = egui::Color32::from_rgb(248, 81, 73); +const TEXT: egui::Color32 = egui::Color32::from_rgb(230, 237, 243); +const TEXT2: egui::Color32 = egui::Color32::from_rgb(139, 148, 158); +const AD_BG: egui::Color32 = egui::Color32::from_rgb(17, 21, 28); 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, + "TGLock", + eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([520.0, 620.0]) + .with_min_inner_size([420.0, 500.0]) + .with_title("TGLock"), + ..Default::default() + }, Box::new(|cc| { - setup_fonts(&cc.egui_ctx); + apply_theme(&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); +fn apply_theme(ctx: &egui::Context) { + let mut v = egui::Visuals::dark(); + v.panel_fill = BG; + v.window_fill = SURFACE; + v.extreme_bg_color = BG; + v.faint_bg_color = SURFACE; + v.override_text_color = Some(TEXT); + + v.widgets.noninteractive.bg_fill = SURFACE; + v.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, TEXT2); + v.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, BORDER); + + v.widgets.inactive.bg_fill = egui::Color32::from_rgb(33, 38, 45); + v.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, TEXT); + v.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, BORDER); + + v.widgets.hovered.bg_fill = egui::Color32::from_rgb(48, 54, 61); + v.widgets.hovered.fg_stroke = egui::Stroke::new(1.0, TEXT); + + v.widgets.active.bg_fill = ACCENT; + v.widgets.active.fg_stroke = egui::Stroke::new(1.0, BG); + + ctx.set_visuals(v); } +// -- Log -------------------------------------------------------------------- + #[derive(Clone)] -struct LogEntry { - text: String, - is_error: bool, +struct LogLine { ts: String, + msg: String, + err: bool, } +fn now_ts() -> String { + let s = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + format!("{:02}:{:02}:{:02}", (s / 3600) % 24, (s / 60) % 60, s % 60) +} + +fn log(log: &Arc>>, msg: &str, err: bool) { + log.lock().unwrap().push(LogLine { + ts: now_ts(), + msg: msg.into(), + err, + }); +} + +// -- App -------------------------------------------------------------------- + struct App { - log: Arc>>, - proxy_stats: Arc, - is_admin: bool, - adapter_name: Arc>>, - dns_set: Arc>, + stats: Arc, + log: Arc>>, + started_at: Option, } impl App { fn new() -> Self { - let is_admin = bypass::check_admin(); - let app = Self { + Self { + stats: proxy::Stats::new(), 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); + started_at: None, } - { - 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 running(&self) -> bool { + self.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; + fn start(&mut self) { + if self.running() { return; } + self.started_at = Some(Instant::now()); + let stats = self.stats.clone(); + let lg = self.log.clone(); + + log(&lg, "Запускаю прокси...", false); 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); + let r = rt.block_on(proxy::run(stats)); + if let Err(e) = r { + log(&lg, &format!("Ошибка: {}", e), true); } }); - std::thread::sleep(std::time::Duration::from_millis(300)); - if self.proxy_running() { - log_msg(&self.log, "Прокси запущен! Настройте Telegram.", false); + std::thread::sleep(std::time::Duration::from_millis(250)); + if self.running() { + log(&self.log, &format!("SOCKS5 на 127.0.0.1:{}", proxy::PORT), false); } } - fn stop_proxy(&self) { - self.proxy_stats.running.store(false, Ordering::SeqCst); - log_msg(&self.log, "Прокси остановлен", false); + fn stop(&mut self) { + self.stats.running.store(false, Ordering::SeqCst); + self.started_at = None; + log(&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 uptime_str(&self) -> String { + match self.started_at { + Some(t) => { + let s = t.elapsed().as_secs(); + format!("{:02}:{:02}:{:02}", s / 3600, (s / 60) % 60, s % 60) + } + None => "--:--:--".into(), } } - - 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>>, 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)); + ctx.request_repaint_after(std::time::Duration::from_millis(300)); - 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); + let on = self.running(); + let active = self.stats.active.load(Ordering::Relaxed); + let total = self.stats.total.load(Ordering::Relaxed); + let ws = self.stats.ws.load(Ordering::Relaxed); + let dc = self.stats.last_dc.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("Прокси не запущен"); - } - }); + // === Ad bar (top) === + egui::TopBottomPanel::top("ad").show(ctx, |ui| { + egui::Frame::new() + .fill(AD_BG) + .inner_margin(egui::Margin::symmetric(12, 6)) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.colored_label(ACCENT, egui::RichText::new("by sonic VPN").size(12.0).strong()); + ui.colored_label(TEXT2, egui::RichText::new("Обход для всех приложений").size(11.0)); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.add(egui::Button::new( + egui::RichText::new("@bysonicvpn_bot").size(11.0).strong().color(ACCENT) + ).frame(false)).clicked() { + let _ = open::that("https://t.me/bysonicvpn_bot"); + } + }); + }); + }); }); - // --- Log panel --- + // === Log (bottom) === egui::TopBottomPanel::bottom("log") - .min_height(130.0) + .min_height(120.0) .show(ctx, |ui| { - ui.label(egui::RichText::new("Лог").strong()); + ui.add_space(4.0); + ui.colored_label(TEXT2, egui::RichText::new("LOG").size(11.0)); 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)); + for e in self.log.lock().unwrap().iter() { + let c = if e.err { RED } else { TEXT2 }; + ui.colored_label(c, egui::RichText::new( + format!("{} {}", e.ts, e.msg) + ).size(11.5).monospace()); } }); }); - // --- Main panel --- - egui::CentralPanel::default().show(ctx, |ui| { - ui.add_space(10.0); - - // --- VPN ad (top) --- - ui.vertical_centered(|ui| { - egui::Frame::new() - .fill(egui::Color32::from_rgb(25, 30, 42)) - .corner_radius(8.0) - .inner_margin(egui::Margin::symmetric(14, 8)) - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.colored_label( - egui::Color32::from_rgb(100, 180, 255), - egui::RichText::new("by sonic VPN").size(13.0).strong(), - ); - ui.label( - egui::RichText::new("Полный обход для всех приложений") - .size(12.0) - .color(egui::Color32::from_rgb(160, 165, 180)), - ); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.add( - egui::Button::new( - egui::RichText::new("@bysonicvpn_bot") - .size(12.0) - .strong() - .color(egui::Color32::from_rgb(100, 200, 255)), - ) - .frame(false), - ).clicked() { - let _ = open::that("https://t.me/bysonicvpn_bot"); - } - }); + // === Stats bar === + egui::TopBottomPanel::bottom("stats").show(ctx, |ui| { + egui::Frame::new() + .fill(SURFACE) + .inner_margin(egui::Margin::symmetric(16, 8)) + .show(ui, |ui| { + ui.horizontal(|ui| { + stat(ui, "Соединения", &active.to_string()); + ui.add_space(20.0); + stat(ui, "WS-туннели", &ws.to_string()); + ui.add_space(20.0); + stat(ui, "DC", &if dc > 0 { dc.to_string() } else { "—".into() }); + ui.add_space(20.0); + stat(ui, "Всего", &total.to_string()); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + stat(ui, "Аптайм", &self.uptime_str()); }); }); - }); - - ui.add_space(12.0); + }); + }); + // === Main === + egui::CentralPanel::default().show(ctx, |ui| { 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); + ui.add_space(30.0); + // Title + ui.colored_label(TEXT, egui::RichText::new("TGLock").size(32.0).strong()); + ui.add_space(4.0); + ui.colored_label(TEXT2, egui::RichText::new("WebSocket-туннель для Telegram").size(13.0)); + + ui.add_space(24.0); + + // Status indicator + let (dot_color, status_text) = if on { + (GREEN, "Подключено") + } else { + (egui::Color32::from_rgb(80, 80, 80), "Отключено") + }; + + ui.horizontal(|ui| { + let center = ui.available_width() / 2.0 - 50.0; + ui.add_space(center); + let (r, _) = ui.allocate_exact_size(egui::vec2(10.0, 10.0), egui::Sense::hover()); + ui.painter().circle_filled(r.center(), 5.0, dot_color); + ui.colored_label( + if on { GREEN } else { TEXT2 }, + egui::RichText::new(status_text).size(14.0).strong(), + ); + }); + + ui.add_space(20.0); + + // Big button + if !on { let btn = ui.add_sized( - [340.0, 55.0], - egui::Button::new(egui::RichText::new("Запустить обход").size(20.0).strong()), + [260.0, 52.0], + egui::Button::new( + egui::RichText::new("ПОДКЛЮЧИТЬ").size(18.0).strong().color(BG) + ).fill(ACCENT).corner_radius(8.0), ); if btn.clicked() { - self.start_proxy(); + self.start(); } } else { - ui.colored_label( - egui::Color32::from_rgb(80, 220, 120), - egui::RichText::new("Обход работает").size(22.0).strong(), + let btn = ui.add_sized( + [260.0, 52.0], + egui::Button::new( + egui::RichText::new("ОТКЛЮЧИТЬ").size(18.0).strong().color(TEXT) + ).fill(egui::Color32::from_rgb(40, 45, 52)).corner_radius(8.0), ); - 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(); + if btn.clicked() { + self.stop(); } } + + ui.add_space(24.0); + + // Setup section + egui::Frame::new() + .fill(SURFACE) + .corner_radius(8.0) + .inner_margin(16.0) + .show(ui, |ui| { + ui.set_width(360.0); + + ui.colored_label(TEXT, egui::RichText::new("Настройка Telegram").size(14.0).strong()); + ui.add_space(6.0); + + if on { + if ui.add(egui::Button::new( + egui::RichText::new("Настроить автоматически").size(13.0).color(ACCENT) + ).frame(false)).clicked() { + let _ = open::that(format!("tg://socks?server=127.0.0.1&port={}", proxy::PORT)); + log(&self.log, "Открываю настройку Telegram...", false); + } + ui.add_space(4.0); + } + + ui.colored_label(TEXT2, egui::RichText::new("Настройки → Продвинутые → Тип соединения → SOCKS5").size(11.5)); + ui.add_space(4.0); + + egui::Grid::new("cfg").num_columns(2).spacing([12.0, 3.0]).show(ui, |ui| { + ui.colored_label(TEXT2, "Сервер"); + ui.monospace("127.0.0.1"); + ui.end_row(); + ui.colored_label(TEXT2, "Порт"); + ui.monospace(format!("{}", proxy::PORT)); + ui.end_row(); + }); + }); + + ui.add_space(16.0); + + // How it works (compact) + ui.colored_label(TEXT2, egui::RichText::new( + "Трафик Telegram → SOCKS5 → WSS → web.telegram.org → DC" + ).size(11.0)); + ui.colored_label(TEXT2, egui::RichText::new( + "Провайдер видит обычный HTTPS. Остальной трафик не затрагивается." + ).size(11.0)); }); - - 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 трафик проходит напрямую без изменений", - ); - }); } } + +fn stat(ui: &mut egui::Ui, label: &str, value: &str) { + ui.vertical(|ui| { + ui.colored_label(TEXT2, egui::RichText::new(label).size(10.0)); + ui.colored_label(TEXT, egui::RichText::new(value).size(13.0).strong().monospace()); + }); +} diff --git a/src/network.rs b/src/network.rs deleted file mode 100644 index 065bf33..0000000 --- a/src/network.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::net::{TcpStream, SocketAddr}; -use std::process::Command; -use std::time::{Duration, Instant}; - -pub fn detect_adapter() -> Option { - 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 { - 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) { - 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 { - // 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::() { - 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::() { - if ms < 10000 { - return Some(ms); - } - } - } - } - None -} - -pub fn tcp_check(ip: &str, port: u16) -> (bool, Option) { - 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) { - 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) -} diff --git a/src/proxy.rs b/src/proxy.rs new file mode 100644 index 0000000..812df74 --- /dev/null +++ b/src/proxy.rs @@ -0,0 +1,232 @@ +use std::net::Ipv4Addr; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering}; +use std::time::Duration; +use tokio::net::{TcpListener, TcpStream}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio_tungstenite::tungstenite; +use tungstenite::client::IntoClientRequest; + +pub const PORT: u16 = 1080; + +pub struct Stats { + pub running: AtomicBool, + pub active: AtomicU32, + pub total: AtomicU32, + pub ws: AtomicU32, + pub last_dc: AtomicU8, +} + +impl Stats { + pub fn new() -> Arc { + Arc::new(Self { + running: AtomicBool::new(false), + active: AtomicU32::new(0), + total: AtomicU32::new(0), + ws: AtomicU32::new(0), + last_dc: AtomicU8::new(0), + }) + } +} + +pub async fn run(stats: Arc) -> Result<(), String> { + let addr = format!("127.0.0.1:{}", PORT); + let listener = TcpListener::bind(&addr) + .await + .map_err(|e| format!("Port {} busy: {}", PORT, e))?; + + stats.running.store(true, Ordering::SeqCst); + + loop { + if !stats.running.load(Ordering::SeqCst) { + break; + } + tokio::select! { + Ok((stream, _)) = listener.accept() => { + let s = stats.clone(); + s.active.fetch_add(1, Ordering::Relaxed); + s.total.fetch_add(1, Ordering::Relaxed); + tokio::spawn(async move { + let _ = handle(stream, &s).await; + s.active.fetch_sub(1, Ordering::Relaxed); + }); + } + _ = tokio::time::sleep(Duration::from_millis(150)) => {} + } + } + + stats.running.store(false, Ordering::SeqCst); + Ok(()) +} + +// -- SOCKS5 ----------------------------------------------------------------- + +async fn handle( + mut s: TcpStream, + stats: &Stats, +) -> Result<(), Box> { + s.set_nodelay(true)?; + + let mut buf = [0u8; 258]; + let n = s.read(&mut buf).await?; + if n < 2 || buf[0] != 0x05 { + return Err("not socks5".into()); + } + s.write_all(&[0x05, 0x00]).await?; + + let n = s.read(&mut buf).await?; + if n < 7 || buf[0] != 0x05 || buf[1] != 0x01 { + s.write_all(&[0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0]).await?; + return Err("bad connect".into()); + } + + let (addr, port) = parse_addr(&buf[3..n])?; + let tg = addr.parse::().ok().and_then(dc_from_ip).is_some(); + + // success reply + s.write_all(&[0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x04, 0x38]).await?; + + if tg { + // Read 64-byte obfuscated2 init → extract real DC + let mut init = [0u8; 64]; + s.read_exact(&mut init).await?; + + let dc = dc_from_init(&init).unwrap_or_else(|| { + addr.parse::().ok().and_then(dc_from_ip).unwrap_or(2) + }); + + stats.last_dc.store(dc, Ordering::Relaxed); + stats.ws.fetch_add(1, Ordering::Relaxed); + + let r = ws_tunnel(s, dc, &init).await; + + stats.ws.fetch_sub(1, Ordering::Relaxed); + r?; + } else { + let remote = TcpStream::connect(format!("{}:{}", addr, port)).await?; + let _ = remote.set_nodelay(true); + tcp_relay(s, remote).await; + } + Ok(()) +} + +fn parse_addr(d: &[u8]) -> Result<(String, u16), Box> { + match d[0] { + 0x01 if d.len() >= 7 => { + Ok((format!("{}.{}.{}.{}", d[1], d[2], d[3], d[4]), + u16::from_be_bytes([d[5], d[6]]))) + } + 0x03 => { + let l = d[1] as usize; + if d.len() < 2 + l + 2 { return Err("short".into()); } + Ok((std::str::from_utf8(&d[2..2 + l])?.into(), + u16::from_be_bytes([d[2 + l], d[3 + l]]))) + } + 0x04 if d.len() >= 19 => { + let mut seg = [0u16; 8]; + for i in 0..8 { seg[i] = u16::from_be_bytes([d[1 + i * 2], d[2 + i * 2]]); } + let ip = std::net::Ipv6Addr::new(seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7]); + Ok((ip.to_string(), u16::from_be_bytes([d[17], d[18]]))) + } + _ => Err("bad addr".into()), + } +} + +// -- DC detection ----------------------------------------------------------- + +fn dc_from_init(init: &[u8; 64]) -> Option { + use aes::Aes256; + use cipher::{KeyIvInit, StreamCipher}; + type Ctr = ctr::Ctr128BE; + + let mut dec = *init; + let mut c = Ctr::new(init[8..40].into(), init[40..56].into()); + c.apply_keystream(&mut dec); + + let id = i32::from_le_bytes([dec[60], dec[61], dec[62], dec[63]]); + let dc = id.unsigned_abs() as u8; + (1..=5).contains(&dc).then_some(dc) +} + +fn dc_from_ip(ip: Ipv4Addr) -> Option { + 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) | (185, 76) => Some(2), + _ => None, + } +} + +fn ws_url(dc: u8) -> String { + format!("wss://kws{}.web.telegram.org/apiws", dc) +} + +// -- WebSocket tunnel ------------------------------------------------------- + +async fn ws_tunnel( + tcp: TcpStream, + dc: u8, + init: &[u8; 64], +) -> Result<(), Box> { + use futures_util::{SinkExt, StreamExt}; + + let mut req = ws_url(dc).as_str().into_client_request()?; + req.headers_mut().insert("Sec-WebSocket-Protocol", "binary".parse()?); + + let tls = native_tls::TlsConnector::new().map_err(|e| format!("tls: {}", e))?; + let connector = tokio_tungstenite::Connector::NativeTls(tls); + + let (mut ws, _) = tokio::time::timeout( + Duration::from_secs(10), + tokio_tungstenite::connect_async_tls_with_config(req, None, false, Some(connector)), + ) + .await + .map_err(|_| "WS connect timeout")? + .map_err(|e| format!("WS: {}", e))?; + + let (mut tcp_r, mut tcp_w) = tokio::io::split(tcp); + + // Send buffered init as first frame + ws.send(tungstenite::Message::Binary(init.to_vec())).await?; + + let mut buf = vec![0u8; 65536]; + + loop { + tokio::select! { + biased; + + msg = ws.next() => match msg { + Some(Ok(tungstenite::Message::Binary(data))) => { + tcp_w.write_all(data.as_ref()).await?; + tcp_w.flush().await?; + } + Some(Ok(tungstenite::Message::Ping(p))) => { + let _ = ws.send(tungstenite::Message::Pong(p)).await; + } + Some(Ok(tungstenite::Message::Close(_))) | None => break, + Some(Err(_)) => break, + _ => {} + }, + + n = tcp_r.read(&mut buf) => match n { + Ok(0) | Err(_) => break, + Ok(n) => { + ws.send(tungstenite::Message::Binary(buf[..n].to_vec())).await?; + } + }, + } + } + + let _ = ws.close(None).await; + Ok(()) +} + +async fn tcp_relay(a: TcpStream, b: TcpStream) { + let (mut ar, mut aw) = tokio::io::split(a); + let (mut br, mut bw) = tokio::io::split(b); + tokio::select! { + _ = tokio::io::copy(&mut ar, &mut bw) => {} + _ = tokio::io::copy(&mut br, &mut aw) => {} + } +} diff --git a/src/ws_proxy.rs b/src/ws_proxy.rs deleted file mode 100644 index 681b1b0..0000000 --- a/src/ws_proxy.rs +++ /dev/null @@ -1,306 +0,0 @@ -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 { - 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) -> 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 extraction from obfuscated2 init packet (same method as tg-ws-proxy) -// --------------------------------------------------------------------------- - -fn extract_dc_from_init(init: &[u8; 64]) -> Option { - use aes::Aes256; - use cipher::{KeyIvInit, StreamCipher}; - type Aes256Ctr = ctr::Ctr128BE; - - let key = &init[8..40]; - let iv = &init[40..56]; - - let mut dec = [0u8; 64]; - dec.copy_from_slice(init); - - let mut cipher = Aes256Ctr::new(key.into(), iv.into()); - cipher.apply_keystream(&mut dec); - - let dc_id = i32::from_le_bytes([dec[60], dec[61], dec[62], dec[63]]); - let dc = dc_id.unsigned_abs() as u8; - if (1..=5).contains(&dc) { - Some(dc) - } else { - None - } -} - -fn dc_from_ip(ip: Ipv4Addr) -> Option { - 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) | (185, 76) => Some(2), - _ => None, - } -} - -fn is_telegram_ip(addr: &str) -> bool { - addr.parse::() - .ok() - .and_then(dc_from_ip) - .is_some() -} - -/// Endpoint format used by the proven tg-ws-proxy project -fn ws_url(dc: u8) -> String { - format!("wss://kws{}.web.telegram.org/apiws", dc) -} - -// --------------------------------------------------------------------------- -// SOCKS5 handler -// --------------------------------------------------------------------------- - -async fn handle_socks5( - mut stream: TcpStream, - stats: &ProxyStats, -) -> Result<(), Box> { - stream.set_nodelay(true)?; - - // --- auth negotiation --- - 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?; - - // --- CONNECT request --- - 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 is_tg = is_telegram_ip(&dest_addr); - - // SOCKS5 success (we handle the connection ourselves) - stream - .write_all(&[0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x04, 0x38]) - .await?; - - if is_tg { - // Read the first 64 bytes — obfuscated2 init packet - let mut init = [0u8; 64]; - stream.read_exact(&mut init).await?; - - // Extract DC from init packet (primary), fall back to IP-based - let dc = extract_dc_from_init(&init).unwrap_or_else(|| { - dest_addr - .parse::() - .ok() - .and_then(dc_from_ip) - .unwrap_or(2) - }); - - stats.ws_active.fetch_add(1, Ordering::Relaxed); - - // Try WebSocket tunnel; fall back to direct TCP on failure - let ws_result = relay_via_ws(stream, dc, &init).await; - - stats.ws_active.fetch_sub(1, Ordering::Relaxed); - - if let Err(e) = ws_result { - return Err(format!("DC{} tunnel: {}", dc, e).into()); - } - } else { - // Non-Telegram — direct TCP passthrough - let target = format!("{}:{}", dest_addr, dest_port); - match TcpStream::connect(&target).await { - Ok(remote) => { - let _ = remote.set_nodelay(true); - relay_tcp(stream, remote).await; - } - Err(e) => { - return Err(format!("TCP connect {}: {}", target, e).into()); - } - } - } - - Ok(()) -} - -fn parse_dest(data: &[u8]) -> Result<(String, u16), Box> { - 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()), - } -} - -// --------------------------------------------------------------------------- -// WebSocket tunnel — reads init first, then relays -// --------------------------------------------------------------------------- - -async fn relay_via_ws( - tcp_stream: TcpStream, - dc: u8, - init: &[u8; 64], -) -> Result<(), Box> { - use futures_util::{SinkExt, StreamExt}; - - let url = ws_url(dc); - let mut request = url.as_str().into_client_request()?; - - request - .headers_mut() - .insert("Sec-WebSocket-Protocol", "binary".parse()?); - - let connector = tokio_tungstenite::Connector::NativeTls( - native_tls::TlsConnector::new().map_err(|e| format!("TLS: {}", e))?, - ); - - let (mut ws, _resp) = tokio_tungstenite::connect_async_tls_with_config( - request, None, false, Some(connector), - ) - .await?; - - let (mut tcp_rx, mut tcp_tx) = tokio::io::split(tcp_stream); - - // Send the buffered 64-byte init as the first WebSocket message - ws.send(tungstenite::Message::Binary(init.to_vec())).await?; - - // Single loop: handles TCP→WS, WS→TCP, and Ping/Pong in one place. - // This ensures Pong replies are sent immediately so the server - // doesn't kill the connection after a timeout. - let mut buf = vec![0u8; 32768]; - - loop { - tokio::select! { - biased; - - ws_msg = ws.next() => { - match ws_msg { - Some(Ok(tungstenite::Message::Binary(data))) => { - if tcp_tx.write_all(data.as_ref()).await.is_err() { - break; - } - } - Some(Ok(tungstenite::Message::Ping(payload))) => { - let _ = ws.send(tungstenite::Message::Pong(payload)).await; - } - Some(Ok(tungstenite::Message::Close(_))) | None => break, - Some(Err(_)) => break, - _ => {} - } - } - - n = tcp_rx.read(&mut buf) => { - match n { - Ok(0) | Err(_) => break, - Ok(n) => { - let msg = tungstenite::Message::Binary(buf[..n].to_vec()); - if ws.send(msg).await.is_err() { - break; - } - } - } - } - } - } - - let _ = ws.close(None).await; - 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) => {} - } -} diff --git a/tg_blacklist.txt b/tg_blacklist.txt deleted file mode 100644 index f554635..0000000 --- a/tg_blacklist.txt +++ /dev/null @@ -1,21 +0,0 @@ -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 diff --git a/tg_unblock.bat b/tg_unblock.bat deleted file mode 100644 index c15f7de..0000000 --- a/tg_unblock.bat +++ /dev/null @@ -1,852 +0,0 @@ -@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 diff --git a/РЕШЕНИЯ.md b/РЕШЕНИЯ.md deleted file mode 100644 index e2a8209..0000000 --- a/РЕШЕНИЯ.md +++ /dev/null @@ -1,418 +0,0 @@ -# Решения задач по теории вероятностей -## Формулы Бернулли, местная и интегральная теоремы Муавра–Лапласа, формула Пуассона - ---- - -## Задача 3 - -**Условие:** В среднем каждое второе малое предприятие имеет нарушение финансовой дисциплины. Из **n = 1000** предприятий найти вероятность того, что нарушения имеют: - -Дано: n = 1000, p = 1/2 = 0,5, q = 0,5 -npq = 1000 · 0,5 · 0,5 = 250, √(npq) = √250 ≈ 15,81 - ---- - -### а) Ровно 480 предприятий - -Используем **местную теорему Муавра–Лапласа**: - -$$P_n(k) \approx \frac{1}{\sqrt{npq}} \cdot \varphi(x), \quad x = \frac{k - np}{\sqrt{npq}}$$ - -$$x = \frac{480 - 500}{15{,}81} = \frac{-20}{15{,}81} \approx -1{,}26$$ - -φ(1,26) = 0,1826 (по таблице функции Гаусса) - -$$P_{1000}(480) \approx \frac{0{,}1826}{15{,}81} \approx \mathbf{0{,}0116}$$ - ---- - -### б) Наивероятнейшее число предприятий - -По формуле: **np − q ≤ k₀ ≤ np + p** - -499,5 ≤ k₀ ≤ 500,5 → **k₀ = 500** - ---- - -### в) Не менее 480 предприятий - -Используем **интегральную теорему Муавра–Лапласа**: - -$$P(k \geq 480) = 0{,}5 + \Phi\!\left(\frac{np - k_1}{\sqrt{npq}}\right)$$ - -$$= 0{,}5 + \Phi(1{,}26) = 0{,}5 + 0{,}3962 \approx \mathbf{0{,}8962}$$ - ---- - -### г) От 480 до 520 предприятий - -$$x_1 = \frac{480 - 500}{15{,}81} \approx -1{,}26, \quad x_2 = \frac{520 - 500}{15{,}81} \approx 1{,}26$$ - -$$P(480 \leq k \leq 520) = \Phi(1{,}26) - \Phi(-1{,}26) = 2\Phi(1{,}26) = 2 \cdot 0{,}3962 \approx \mathbf{0{,}7924}$$ - ---- - -## Задача 4 - -**Условие:** Вероятность банкротства предприятия за время t равна 0,2. Из **n = 6** предприятий найти вероятность того, что **сохранятся**: - -Дано: n = 6, p(сохранится) = 1 − 0,2 = **0,8**, q = 0,2 - -Используем **формулу Бернулли**: P_n(k) = C(n,k) · pᵏ · qⁿ⁻ᵏ - ---- - -### а) Два предприятия (k = 2) - -$$P_6(2) = C_6^2 \cdot (0{,}8)^2 \cdot (0{,}2)^4 = 15 \cdot 0{,}64 \cdot 0{,}0016 = \mathbf{0{,}0154}$$ - ---- - -### б) Более двух предприятий (k > 2) - -$$P(k > 2) = 1 - P(k \leq 2) = 1 - [P(0) + P(1) + P(2)]$$ - -- P₆(0) = C(6,0) · (0,8)⁰ · (0,2)⁶ = 1 · 1 · 0,000064 = 0,000064 -- P₆(1) = C(6,1) · (0,8)¹ · (0,2)⁵ = 6 · 0,8 · 0,00032 = 0,001536 -- P₆(2) = 0,0154 (вычислено выше) - -$$P(k > 2) = 1 - (0{,}000064 + 0{,}001536 + 0{,}0154) \approx 1 - 0{,}0170 = \mathbf{0{,}983}$$ - ---- - -## Задача 5 - -**Условие:** В банк отправлено **n = 4000** пакетов. Вероятность ошибки в комплектации **p = 0,0001**. Найти вероятность обнаружения: - -λ = np = 4000 · 0,0001 = **0,4** - -Используем **формулу Пуассона**: P(k) = (λᵏ / k!) · e⁻λ - -e⁻⁰·⁴ ≈ 0,6703 - ---- - -### а) Три ошибочно укомплектованных пакета (k = 3) - -$$P(3) = \frac{(0{,}4)^3}{3!} \cdot e^{-0{,}4} = \frac{0{,}064}{6} \cdot 0{,}6703 \approx \mathbf{0{,}00715}$$ - ---- - -### б) Не более трёх пакетов (k ≤ 3) - -$$P(k \leq 3) = P(0) + P(1) + P(2) + P(3)$$ - -- P(0) = e⁻⁰·⁴ = 0,6703 -- P(1) = 0,4 · e⁻⁰·⁴ = 0,2681 -- P(2) = (0,4²/2) · e⁻⁰·⁴ = 0,0536 -- P(3) = 0,00715 - -$$P(k \leq 3) = 0{,}6703 + 0{,}2681 + 0{,}0536 + 0{,}00715 \approx \mathbf{0{,}9992}$$ - ---- - ---- - -## Домашняя работа - ---- - -## Задача 1 (ДЗ) - -**Условие:** Вероятность попадания в цель р = 0,7. Из **n = 80** выстрелов — найти вероятность различных исходов. - -Дано: n = 80, p = 0,7, q = 0,3 -np = 56, npq = 16,8, √(npq) ≈ 4,10 - ---- - -### а) Ровно 75 попаданий - -$$x = \frac{75 - 56}{4{,}10} \approx 4{,}63$$ - -φ(4,63) ≈ 0,00002 (крайне малое значение) - -$$P_{80}(75) \approx \frac{0{,}00002}{4{,}10} \approx \mathbf{0{,}000005} \approx 0$$ - ---- - -### б) Не менее 75 попаданий - -$$x_1 = \frac{75 - 56}{4{,}10} \approx 4{,}63$$ - -$$P(k \geq 75) = 0{,}5 - \Phi(4{,}63) \approx 0{,}5 - 0{,}5 \approx \mathbf{0}$$ - ---- - -### в) Менее 75 попаданий - -$$P(k < 75) = 1 - P(k \geq 75) \approx \mathbf{1}$$ - ---- - -### г) Не более 75 попаданий - -$$P(k \leq 75) = P(k < 75) + P(k = 75) \approx 1 + 0 \approx \mathbf{1}$$ - ---- - -### д) Более 75 попаданий - -$$P(k > 75) \approx \mathbf{0}$$ - ---- - -### е) Все 80 выстрелов - -$$P_{80}(80) = (0{,}7)^{80} = e^{80 \ln 0{,}7} = e^{-28{,}5} \approx \mathbf{4{,}4 \times 10^{-13}}$$ - ---- - -## Задача 2 (ДЗ) - -**Условие:** Вероятность выпуска сверла с браком p = 0,02. Сверла укладываются по 100 штук. Найти наименьшее количество добавочных сверл, чтобы с вероятностью **не менее 0,9** в коробке было **не менее 100 исправных**. - -Пусть в коробке **n** сверл, p(исправное) = 0,98. Нужно: P(X ≥ 100) ≥ 0,9 - -$$P(X \geq 100) = 0{,}5 + \Phi\!\left(\frac{np - 100}{\sqrt{np \cdot 0{,}02}}\right) \geq 0{,}9$$ - -$$\Rightarrow \frac{0{,}98n - 100}{0{,}14\sqrt{n}} \geq 1{,}28$$ - -Подстановка t = √n: 0,98t² − 0,1792t − 100 ≥ 0 - -$$t = \frac{0{,}1792 + \sqrt{0{,}1792^2 + 4 \cdot 0{,}98 \cdot 100}}{2 \cdot 0{,}98} = \frac{0{,}179 + 19{,}80}{1{,}96} \approx 10{,}19$$ - -$$n \geq (10{,}19)^2 \approx 103{,}9 \Rightarrow n = 104$$ - -**Ответ:** нужно добавить **m = 104 − 100 = 4 сверла** - ---- - -## Задача 3 (ДЗ) - -**Условие:** Сколько изюминок в среднем должна содержать булочка, чтобы вероятность хотя бы одной изюминки была **не менее 0,99**? - -Используем формулу Пуассона: P(X ≥ 1) = 1 − P(X = 0) = 1 − e⁻λ ≥ 0,99 - -$$e^{-\lambda} \leq 0{,}01 \Rightarrow \lambda \geq -\ln(0{,}01) = \ln 100 \approx 4{,}61$$ - -**Ответ:** в среднем в булочке должно быть **не менее 5 изюминок** (λ ≥ 4,61) - ---- - ---- - -## Задача (Картофель) - -**Условие:** В складе 20% клубней с пятнами. Отобрано **n = 9** клубней. p(без пятен) = 0,8, q = 0,2. - ---- - -### а) Наивероятнейшее число клубней без пятен - -np − q ≤ k₀ ≤ np + p -9·0,8 − 0,2 ≤ k₀ ≤ 9·0,8 + 0,8 -**7,0 ≤ k₀ ≤ 8,0** - -Так как оба конца — целые числа, оба значения k₀ = 7 и k₀ = 8 являются наивероятнейшими (с одинаковой вероятностью). - ---- - -### б) Вероятность наивероятнейшего числа - -$$P_9(7) = C_9^7 \cdot (0{,}8)^7 \cdot (0{,}2)^2 = 36 \cdot 0{,}2097 \cdot 0{,}04 \approx \mathbf{0{,}302}$$ - -$$P_9(8) = C_9^8 \cdot (0{,}8)^8 \cdot (0{,}2)^1 = 9 \cdot 0{,}1678 \cdot 0{,}2 \approx \mathbf{0{,}302}$$ - -P(7) = P(8) ≈ **0,302** — подтверждает, что оба значения равновероятны - ---- - -## Задача (Событие А, k₀ = 20) - -**Условие:** Вероятность события А в каждом испытании p = 0,7. Сколько испытаний нужно провести, чтобы **наивероятнейшее число** равнялось **20**? - -np − q ≤ k₀ ≤ np + p, то есть при k₀ = 20: - -$$0{,}7n - 0{,}3 \leq 20 \leq 0{,}7n + 0{,}7$$ - -Из левого неравенства: 0,7n ≤ 20,3 → **n ≤ 29** - -Из правого неравенства: 0,7n ≥ 19,3 → **n ≥ 27,57 → n ≥ 28** - -Проверка: -- n = 28: np−q = 19,3, np+p = 20,3 → единственное целое k₀ = **20** ✓ -- n = 29: np−q = 20,0, np+p = 21,0 → k₀ = 20 или 21 (два наивероятнейших) - -**Ответ: n = 28** (или n = 29, если допустимо два наивероятнейших значения) - ---- - ---- - -## Задача 1 (Лотерея) - -**Условие:** Вероятность выигрыша по одному билету p = 1/7. Имея **n = 7** билетов, найти вероятность выигрыша: - -Используем **формулу Бернулли** - ---- - -### а) По двум билетам (k = 2) - -$$P_7(2) = C_7^2 \cdot \left(\frac{1}{7}\right)^2 \cdot \left(\frac{6}{7}\right)^5 = 21 \cdot \frac{1}{49} \cdot \frac{7776}{16807} = \frac{163296}{823543} \approx \mathbf{0{,}198}$$ - ---- - -### б) По трём билетам (k = 3) - -$$P_7(3) = C_7^3 \cdot \left(\frac{1}{7}\right)^3 \cdot \left(\frac{6}{7}\right)^4 = 35 \cdot \frac{1}{343} \cdot \frac{1296}{2401} = \frac{45360}{823543} \approx \mathbf{0{,}055}$$ - ---- - -## Задача 2 (Мята, гербициды) - -**Условие:** Повреждены гербицидами 15% растений мяты. Отобрано **n = 20** растений. p = 0,15, q = 0,85. - -np − q ≤ k₀ ≤ np + p -3 − 0,85 ≤ k₀ ≤ 3 + 0,15 -2,15 ≤ k₀ ≤ 3,15 - -**k₀ = 3** — наивероятнейшее число повреждённых растений - ---- - -## Задача 3 (Сбербанк) - -**Условие:** Два филиала. Филиал 1: n₁ = 120, p₁ = 0,94. Филиал 2: n₂ = 140, p₂ = 0,8. Найти наивероятнейшее число клиентов, снявших деньги. - -**Филиал 1:** np₁ = 112,8; np₁ − q₁ = 112,74; np₁ + p₁ = 113,74 → **k₀₁ = 113** - -**Филиал 2:** np₂ = 112,0; np₂ − q₂ = 111,8; np₂ + p₂ = 112,8 → **k₀₂ = 112** - -**Ответ:** Первый филиал обслуживает больше клиентов, снявших деньги: **113 > 112** - ---- - ---- - -## Задача (Событие А, 400 испытаний) - -**Условие:** Найти вероятность того, что событие А наступит **ровно 80 раз** в 400 испытаниях, если p = 0,2. - -Дано: n = 400, k = 80, p = 0,2, q = 0,8 -np = 80, npq = 64, √(npq) = 8 - -$$x = \frac{k - np}{\sqrt{npq}} = \frac{80 - 80}{8} = 0$$ - -$$\varphi(0) = \frac{1}{\sqrt{2\pi}} \approx 0{,}3989$$ - -$$P_{400}(80) \approx \frac{\varphi(0)}{\sqrt{npq}} = \frac{0{,}3989}{8} \approx \mathbf{0{,}0499}$$ - ---- - -## Задача (Стрелок) - -**Условие:** Вероятность поражения мишени p = 0,75. При **n = 10** выстрелах найти вероятность **ровно 8 попаданий**. - -$$P_{10}(8) = C_{10}^8 \cdot (0{,}75)^8 \cdot (0{,}25)^2$$ - -$$= 45 \cdot \frac{6561}{65536} \cdot \frac{1}{16} = \frac{295245}{1048576} \approx \mathbf{0{,}2816}$$ - ---- - ---- - -## Задача (Кинескопы — Интегральная теорема Лапласа) - -**Условие:** 12% кинескопов не проработают гарантийный срок. Из **n = 50** наугад выбранных — найти вероятность того, что проработают гарантийный срок: - -Дано: n = 50, p(проработает) = 1 − 0,12 = **0,88**, q = 0,12 -np = 44, npq = 50 · 0,88 · 0,12 = 5,28, √(npq) ≈ 2,298 - ---- - -### а) Ровно 47 кинескопов - -Местная теорема: - -$$x = \frac{47 - 44}{2{,}298} \approx 1{,}305, \quad \varphi(1{,}305) \approx 0{,}1714$$ - -$$P_{50}(47) \approx \frac{0{,}1714}{2{,}298} \approx \mathbf{0{,}0745}$$ - ---- - -### б) Не менее 47 кинескопов - -$$x_1 = \frac{47 - 44}{2{,}298} \approx 1{,}305$$ - -$$P(k \geq 47) \approx 0{,}5 - \Phi(1{,}305) = 0{,}5 - 0{,}4040 \approx \mathbf{0{,}0960}$$ - ---- - -### в) Менее 47 кинескопов - -$$P(k < 47) = 1 - P(k \geq 47) \approx 1 - 0{,}0960 = \mathbf{0{,}9040}$$ - ---- - -### г) Более 47 кинескопов - -$$x = \frac{48 - 44}{2{,}298} \approx 1{,}740$$ - -$$P(k > 47) = P(k \geq 48) \approx 0{,}5 - \Phi(1{,}74) = 0{,}5 - 0{,}4591 \approx \mathbf{0{,}0409}$$ - ---- - -### д) Не более 47 кинескопов - -$$P(k \leq 47) = 1 - P(k > 47) \approx 1 - 0{,}0409 = \mathbf{0{,}9591}$$ - ---- - -### е) Все 50 кинескопов - -$$P_{50}(50) = (0{,}88)^{50} \approx \mathbf{0{,}00167}$$ - -(Для проверки по местной теореме: x = (50−44)/2,298 ≈ 2,61; φ(2,61) ≈ 0,0136; P ≈ 0,006 — приближение менее точно из-за удалённости от центра) - ---- - ---- - -## Домашнее задание (Интегральная теорема Лапласа) - ---- - -## Задача 1 (ДЗ — Новорождённые) - -**Условие:** Среди **n = 1000** новорождённых. Вероятность рождения мальчика p = 0,51. Найти вероятность того, что мальчиков будет: - -np = 510, npq = 1000 · 0,51 · 0,49 = 249,9, √(npq) ≈ 15,81 - ---- - -### а) Не менее половины (m ≥ 500) - -$$x_1 = \frac{500 - 510}{15{,}81} \approx -0{,}63, \quad x_2 \to +\infty$$ - -$$P_{1000}(500 \leq m \leq 1000) \approx \Phi(+\infty) - \Phi(-0{,}63) = 0{,}5 - (-0{,}2357) = \mathbf{0{,}7357}$$ - ---- - -### б) Менее половины (m < 500, то есть m ≤ 499) - -$$P_{1000}(0 \leq m \leq 499) = 1 - 0{,}7357 = \mathbf{0{,}2643}$$ - ---- - -## Задача 2 (ДЗ — Картофель при уборке) - -**Условие:** При уборке повреждается в среднем 10% клубней. Из **n = 200** клубней найти вероятность того, что повреждено от 15 до 50 клубней. - -Дано: n = 200, p = 0,10, q = 0,90 -np = 20, npq = 18, √(npq) ≈ 4,243 - -$$x_1 = \frac{15 - 20}{4{,}243} \approx -1{,}18, \quad x_2 = \frac{50 - 20}{4{,}243} \approx 7{,}07$$ - -$$P_{200}(15 \leq m \leq 50) \approx \Phi(7{,}07) - \Phi(-1{,}18) \approx 0{,}5 - (-0{,}3810) = 0{,}5 + 0{,}3810 \approx \mathbf{0{,}881}$$