diff --git a/BETA.md b/BETA.md index 990cb1f..a42a5d2 100644 --- a/BETA.md +++ b/BETA.md @@ -1,10 +1,11 @@ # Каналы PCA Phobos -С версии **1.4.0** все три канала (stable / beta / dev) — одна и та же версия. -Новые фичи по-прежнему сначала выкатываются в `dev`, затем в `beta`, затем в `main` -(`promote.sh`), но база одна — без расхождений. +С **1.5.0** все три канала — одна и та же панель и одни и те же серверные функции. -```bash -phobos-update # stable (открытый) -GH_TOKEN=… phobos-update beta|dev # для подписчиков Boosty -``` +| Канал | Для кого | Отличие | +|---|---|---| +| **stable** (`main`, открытый) | все | без приложений Apple: в окне «Подключение» нет вкладок iOS и macOS | +| **beta** | подписчики Boosty (токен) | + приложения PCA Phobos VPN для iPhone (TestFlight) и macOS (.pkg) | +| **dev** | подписчики, тест нового | то же, что beta, новые функции появляются здесь первыми | + +Переключение: `phobos-update stable|beta|dev` или «Настройки → Версия и обновления». Токен подписчика: там же. diff --git a/CHANGELOG.md b/CHANGELOG.md index b94c0f5..f38c01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.5.0 — 2026-11-11 +- **Одна версия во всех каналах.** stable получил всё из beta: новый дизайн (боковое меню, «Сессии» с графиками, окна «Правка» / «Подключение» / «Роутер»), пресеты обфускатора, firewall по клиенту, трафик за месяц, DNS для клиентов, автопереход роутеров по всем портам и адресам + TCP, второй IP сервера (`phobos-altip`), несколько учёток на роутере, IPv6 для телефонов, вкладки Windows и HR, баннер «Доступна новая версия». +- **Приложения Apple — только beta/dev.** В beta/dev окно «Подключение» показывает вкладки **iOS** и новую **macOS** (установщик PCA Phobos для macOS 1.4.3 скачивается из закрытого релиза по токену подписчика). В stable этих вкладок нет, у iPhone — только обычный WireGuard; исходники iOS/macOS/Windows в открытый репозиторий не входят. +- Меню: при новой версии под «Настройками» — зелёный пункт «Обновить до N». На телефоне верхнее меню больше не раздувается до 200 px. +- Скачивание из git.bobyrev.com (Windows, macOS, проверка версий) — свой User-Agent: Cloudflare отвечал 403 на «Python-urllib». +- Ссылка на Android-приложение — релиз v1.4.10 (PCA Phobos VPN 1.4.6). +- Документация: источник — GitHub adleibzon-rgb (git.bobyrev.com — запасной), разделы «Пароль панели», «Удаление», «Подключение устройств», свежие скриншоты; PORTS.md — TCP-резерв и второй IP. + ## 1.4.11 — 2026-11-11 - Панель сама раз в час проверяет свой канал (stable/beta/dev): если вышла новая версия — сверху зелёная полоса «Доступна новая версия» и кнопка «Обновить». В окне перед обновлением — что нового, как сделать бэкап и как откатиться. diff --git a/README.md b/README.md index 5578924..10826fe 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - 📡 **Работает за любым NAT** — серый IP, мобильный интернет, домашний роутер за провайдером. Управление едет внутри самого защищённого туннеля, поэтому бан публичного IP ему не страшен. - 🖥️ **Одна команда — новое устройство** — создал клиента в панели → вставил команду на роутер → готово. - ♻️ **Само-восстановление** — роутер перезагрузился, провайдер сменил IP, сервис отвалился → система поднимет всё обратно сама. -- 📱 **Телефоны** — своё приложение **PCA Phobos VPN** для Android (обфускация + раздельный трафик по приложениям), PhobosWG, iPhone (обычный WireGuard) — через QR/ссылку. +- 📱 **Телефоны и ПК** — свои приложения **PCA Phobos VPN**: Android (APK) и Windows (по токену подписчика). Плюс PhobosWG и обычный WireGuard — через QR/ссылку. Приложения для iPhone и macOS — в каналах beta/dev. - 🛠 **Админ-приложение** PCA Phobos для Android — все ваши панели в одном месте. --- @@ -29,7 +29,7 @@ - **Мульти-сервер + failover** — приоритеты, балансировка по нагрузке (CPU/RAM + пропускная способность канала), резервные серверы; роутеры переключаются автоматически. - **NAT-friendly управление** — роутеры сами тянут конфиг по туннелю (`10.25.0.1`), переживает бан публичного IP. - **Авто-восстановление роутеров** — серверный сторож (watchdog) поднимает роутер после неудачной перезагрузки через KeenDNS. -- **Конфиги для телефона** — 📱 Android `phobos://` + QR, 🍎 iPhone обычный WireGuard + QR. +- **Окно «Подключение»** — Android / Windows / Router / HR (+ обычный WireGuard для iPhone). - **Метки** — имя объекта по IP (видно в сессиях и Telegram). - **Срок подписки** — дата окончания, обратный отсчёт, автокик + предупреждения. - **Telegram-уведомления** — подключение/отключение, сервер вверх/вниз, истечение подписки, авто-восстановление. @@ -43,6 +43,13 @@ - **Сторож** — блокировка зондов и сканеров РКН (nftables, список обновляется раз в сутки). - **WARP / WARP+** — выпуск клиентов в интернет через Cloudflare WARP (всем или выборочно), relay-режим. - **Бэкап и восстановление** сервера из панели; **терминал роутера в браузере** (ttyd/RCI) за NAT. +- **Новый дизайн (с 1.5.0 во всех каналах)** — боковое меню, «Сессии» с графиками CPU/RAM/скорости, кольцо трафика по клиентам; клиенты — таблица и окна «Правка», «Подключение» (Android / iOS / Windows / Router / **HR** для HydraRoute), «Роутер» (SSH / RCI / TTYD). +- **Пресеты обфускатора** — свой набор портов и ключ на группу клиентов; **firewall по клиенту** (🛡 — только разрешённые адреса). +- **Трафик за месяц** по каждому клиенту (сумма по всем серверам), история по месяцам. +- **DNS для клиентов** — задаётся в «Настройках». +- **Автопереход роутера (1.5.0)** — при сбое сторож перебирает все порты и все адреса сервера (второй IP, домены), затем TCP; на основной возвращается сам, если там снова есть рукопожатие. +- **Второй IP сервера** (`phobos-altip`) — если провайдер клиента режет основной адрес, роутер уходит на второй. +- **Несколько учёток на одном роутере**, **IPv6 для телефонов**, **ссылка для HydraRoute** (строго по формату автора Phobos). - **Версии и обновления** — обновление и откат прямо из панели. - **Уведомления per-client** — 🔔 у каждого клиента: включить/выключить Telegram-оповещения о подключении/отключении. - **Telegram-бот** — управление клиентами командами прямо из чата: @@ -56,8 +63,20 @@ ## Скриншоты -| Клиенты (светлая) | Клиенты (тёмная) | +| Сессии (светлая) | Сессии (тёмная) | |---|---| +| ![Сессии](docs/img/sessions.png) | ![Сессии тёмная](docs/img/sessions-dark.png) | +| **Клиенты** | **Серверы: второй сервер, домен для клиентов** | +| ![Клиенты](docs/img/clients.png) | ![Серверы](docs/img/servers.png) | + +| Телефон: клиенты | Телефон: тёмная | Телефон: серверы | +|---|---|---| +| ![](docs/img/mobile-clients.png) | ![](docs/img/mobile-clients-dark.png) | ![](docs/img/mobile-servers.png) | + +Настройки (пароль, домен, каскад, DNS, сторож, обновления, WARP, бэкап): [светлая](docs/img/settings.png) · [тёмная](docs/img/settings-dark.png). +Android-приложения: [VPN-клиент](docs/img/android-vpn.png) · [раздельный трафик](docs/img/android-vpn-apps.png) · [админ](docs/img/android-admin.png). + +---|---| | ![Клиенты](docs/img/clients.png) | ![Клиенты тёмная](docs/img/clients-dark.png) | | **Сессии** | **Серверы: замер скорости, второй сервер** | | ![Сессии](docs/img/sessions.png) | ![Серверы](docs/img/servers.png) | @@ -68,31 +87,28 @@ Настройки (домен, каскад, сторож, WARP, бэкап): [светлая](docs/img/settings.png) · [тёмная](docs/img/settings-dark.png) ---------|---------|---------| -| ![Сессии](docs/img/sessions.png) | ![Клиенты](docs/img/clients.png) | ![Серверы](docs/img/servers.png) | - -> Если картинки не отображаются — положи свои PNG в `docs/img/` (`sessions.png`, `clients.png`, `servers.png`). - --- -> **Источник с 23.10.2026:** `git.bobyrev.com/PCA/PCA_Phobos` (GitHub-аккаунт временно недоступен). Серверы на 1.2.x обновляются одной командой: +> **Откуда ставится (с 11.11.2026):** основной источник — GitHub [`adleibzon-rgb/PCA_Phobos`](https://github.com/adleibzon-rgb/PCA_Phobos), запасной — зеркало `git.bobyrev.com/PCA/PCA_Phobos` (обновлятор сам переходит на него, если GitHub недоступен). Старый аккаунт `andrey271192` заблокирован — ссылки на него не работают. +> Серверы на старых версиях (1.2.x–1.4.x) обновляются одной командой: > ```bash -> bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/update.sh) +> bash <(curl -fsSL https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/update.sh) > ``` +> Если GitHub с сервера не открывается — то же с зеркала: `bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/update.sh)` ## Быстрый старт (с чистого VPS, одной командой) Ставит **всё** со всеми зависимостями: wg-obfuscator, WireGuard, обфускатор-сервисы, веб-панель, nginx, скрипты онбординга роутеров, сторож авто-восстановления. Предустановленный Phobos **не требуется**. ```bash -bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh) +bash <(curl -fsSL https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/install.sh) ``` ### С параметрами ```bash PANEL_PASS=МойПароль TG_TOKEN=123:abc TG_CHAT=123456789 \ -bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh) +bash <(curl -fsSL https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/install.sh) ``` | Переменная | По умолчанию | Описание | @@ -115,7 +131,7 @@ DNS клиентов и свой AdGuard Home: [docs/DNS.md](docs/DNS.md). WARP ```bash MAIN_PROTO=http MAIN_SERVER= MAIN_PORT=<порт_панели> MAIN_API_KEY= \ -bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/secondary-setup.sh) +bash <(curl -fsSL https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/server/secondary-setup.sh) ``` Сервер сам зарегистрируется в панели, клиенты досылаются на него сразу. Если сервер за NAT — добавь `SERVER_IP=<внешний_ip>`. @@ -125,7 +141,7 @@ bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/ На входном VPS (чистый Debian/Ubuntu, Phobos не нужен): ```bash -bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/phobos-cascade.sh) enable 2083,5443,993 +bash <(curl -fsSL https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/server/phobos-cascade.sh) enable 2083,5443,993 ``` Затем в панели выходного сервера → **Настройки → Адрес для клиентов и каскад** → впиши IP/домен входа. Роутеры переключатся сами (~15 с), телефонам — заново открыть ссылку/QR. Отключить: `phobos-cascade disable`. @@ -140,38 +156,39 @@ bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/ ## Приложения для Android -- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), **российские сайты напрямую** (по умолчанию), **канал Авто/UDP/TCP/Облако** (при блокировке UDP — сам на TCP), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.4/PCA-Phobos-VPN.apk) · исходники `android-client/`. +- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), **российские сайты напрямую** (по умолчанию), **канал Авто/UDP/TCP/Облако** (при блокировке UDP — сам на TCP), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.10/PCA-Phobos-VPN.apk) · исходники `android-client/`. - **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.4/PCA-Phobos.apk) · исходники `android/`. -## Клиенты для телефона +## Подключение устройств -В таблице **Клиенты** у каждого клиента есть кнопки: +Клиенты → у клиента окно **«Подключение»** с вкладками: -- 📱 **Android** — `phobos://`-ссылка + QR для приложения **PhobosWG** (с обфускацией). Скан QR → импорт. -- 🍎 **iPhone / iOS** — обычный WireGuard-конфиг + QR для официального WireGuard (**без** обфускации). Требует открытого порта 51820 (`ALLOW_PLAIN_WG=1` при установке). +- **Android** — `phobos://`-ссылка + QR для **PCA Phobos VPN** или **PhobosWG** (с обфускацией), файл `.conf`. +- **Windows** — ссылка `phobos://` и кнопка скачивания **PCA Phobos для Windows** (архив из закрытого релиза, нужен токен подписчика в «Настройках»). +- **Router** — команда установки на Keenetic/OpenWrt (Entware) и команда удаления. +- **HR** — ссылка для HydraRoute Neo строго по формату автора Phobos. +- 🍎 **iPhone** в stable — только обычный WireGuard (без обфускации, нужен порт 51820, `ALLOW_PLAIN_WG=1`). Приложения **PCA Phobos VPN для iPhone и macOS** (с обфускацией, автопереход) — в каналах **beta/dev** для подписчиков Boosty. > Для каждого устройства создавай **отдельного клиента** (свой ключ и IP). Один конфиг на двух устройствах = конфликт ключей. -**Приложения:** -- **Android (PhobosWG):** кнопка «Скачать приложение PhobosWG (APK)» на странице Android-клиента. Можно положить свой `.apk` в `/opt/Phobos/www/app/PhobosWG.apk` — тогда панель раздаёт его. -- **iPhone (iOS):** официальный [WireGuard из App Store](https://apps.apple.com/app/wireguard/id1441195209) — кнопка есть на странице iOS-клиента. - --- ## Обновление и откат Проект версионируется (см. файл `VERSION` и git-теги `vX.Y.Z`). -**Из панели:** Настройки → «Версия и обновления» → **Обновить** / **Откатить**. +**Из панели:** Настройки → «Версия и обновления» → **Обновить** / **Откатить**. С 1.4.11 панель сама раз в час проверяет свой канал: вышла новая версия — сверху зелёная полоса «Доступна новая версия N» с кнопкой **Обновить** (в окне — что нового, бэкап, откат). Там же видно, если вышли новые версии приложений. В меню под «Настройками» в это время горит зелёный пункт **«Обновить до N»**. + +> Панели до 1.4.11 этой полосы не показывают — один раз обнови вручную (`phobos-update`), дальше она появляется сама. **Из консоли:** ```bash phobos-update # обновить до последней версии (stable) -phobos-update beta|dev # каналы для подписчиков (GH_TOKEN), сейчас = та же 1.4.0 +phobos-update beta|dev # каналы для подписчиков Boosty (токен сохраняется в /opt/phobos-panel/.gh_token) phobos-update v1.1.0 # обновить до конкретной версии (git tag) phobos-update --rollback # откатить на предыдущую версию -phobos-update --version # показать установленную и доступную версию +phobos-update --check # установленная версия и версии всех каналов phobos-update --list # список бэкапов ``` @@ -179,11 +196,53 @@ phobos-update --list # список бэкапов --- +## Пароль панели — сменить или вспомнить + +- **Сменить из панели:** «Настройки» → поле **«Пароль панели»** → новый пароль → «Сохранить». Пустое поле — пароль не меняется. Логин всегда `admin`. +- **Вспомнить:** `grep admin_pass /opt/phobos-panel/settings.json` +- **Сменить без входа (из консоли сервера):** + ```bash + python3 - <<'EOF' + import json; p="/opt/phobos-panel/settings.json"; s=json.load(open(p)) + s["admin_pass"]="НовыйПароль"; json.dump(s, open(p,"w"), ensure_ascii=False, indent=2) + EOF + systemctl restart phobos-panel + ``` +- Пароль по умолчанию (если не задавал `PANEL_PASS` при установке): `OcAdmin2026!` — смени сразу после установки. +- Порт панели: `cat /opt/phobos-panel/.port` · API key: `grep server_api_key /opt/phobos-panel/settings.json`. + +--- + +## Удаление + +**Сервер целиком** (панель, обфускатор, WireGuard `wg0`, nginx-сайт, сторож, правила iptables, каталоги Phobos): + +```bash +bash <(curl -fsSL https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/uninstall.sh) +``` + +Спросит подтверждение — ответь `yes` (или `y` / `да`; до 1.4.6 работал только полный `yes`). Без вопросов: `PURGE=1 bash <(curl -fsSL …/uninstall.sh)`. Перед удалением сохрани бэкап из панели («Настройки» → «Бэкап»), если сервер понадобится снова. Клиенты на других серверах при удалении одного сервера продолжат работать (если есть резервный). + +**Один клиент** — в панели «Клиенты» → 🗑 у клиента: ключ отзывается на всех серверах, роутер/телефон больше не подключится. + +**С роутера Keenetic** (по SSH в Entware) — команда есть и в панели, внизу окна установки роутера («Удалить с роутера»): + +```bash +/opt/etc/Phobos/phobos-uninstall.sh +``` + +Скрипт останавливает обфускатор, удаляет его конфиги и сам удаляет интерфейс WireGuard Phobos на роутере. + +**Телефон / Windows:** удалить туннель в приложении и само приложение; в Windows — `uninstall.cmd` из папки программы. + +--- + ## Возможные проблемы и решения | Симптом | Причина | Решение | |---------|---------|---------| -| **Команда установки → 403 / не качается** | репозиторий приватный | Сделай репозиторий публичным, либо ставь с токеном: `GH_TOKEN=ghp_xxx bash <(curl -fsSL -H "Authorization: token ghp_xxx" .../install.sh)` | +| **Команда установки → 403 / 404 / не качается** | старая ссылка (`andrey271192` заблокирован) или GitHub недоступен с сервера | Ссылки только `github.com/adleibzon-rgb/…`; если GitHub не открывается — ставь с зеркала `git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh` | +| **Удаление: «Отменено»** | старая версия uninstall.sh понимала только `yes` | Набери `yes` полностью или `PURGE=1` | | **Панель не открывается** | панель перезапускалась (обновление) / сервер недоступен | Подожди 5 сек, обнови страницу. Проверь: `systemctl status phobos-panel` | | **Клиент Offline, хотя работает** | роутер на резервном сервере | Норма — статус считается по handshake на **любом** сервере; обнови страницу | | **«SSH timeout» при тесте роутера** | роутер за NAT, входящий SSH закрыт | Это нормально — управление идёт по pull-каналу через туннель, SSH не нужен | @@ -192,7 +251,7 @@ phobos-update --list # список бэкапов | **iPhone не подключается по `phobos://`** | на iOS нет приложения-обфускатора | Используй 🍎-конфиг (обычный WireGuard) + открой 51820 (`ALLOW_PLAIN_WG=1`) | | **Смена сервера применяется не сразу** | роутер тянет конфиг по cron | Применяется за ~12–15 сек, не мгновенно | | **Забыл порт панели** | случайный порт | `cat /opt/phobos-panel/.port` | -| **Забыл пароль панели** | пароль лежит открытым текстом | `grep admin_pass /opt/phobos-panel/settings.json` (логин `admin`). Сменить без входа: поправь `admin_pass` в том же файле и `systemctl restart phobos-panel`. Если не менял при установке — `OcAdmin2026!` | +| **Забыл пароль панели** | | см. раздел [«Пароль панели»](#пароль-панели--сменить-или-вспомнить) | | **Забыл API key** | | `grep server_api_key /opt/phobos-panel/settings.json` | | **Домен: certbot failed** | A-запись не на этот сервер / закрыт 80 | `dig +short домен` = IP сервера; открой 80/tcp; повтори в «Настройках» | | **Лимит скорости не действует** | wg0 перезапущен | панель сама восстановит за ≤1 мин; вручную: `phobos-limit.sh apply` | @@ -237,3 +296,6 @@ PCA Phobos — это веб-панель и turnkey-инсталлятор по - [**wg-obfuscator**](https://github.com/ClusterM/wg-obfuscator) — обфускация WireGuard-трафика (GPL-3.0), автор **ClusterM**. Поддержать: [Boosty](https://boosty.to/cluster) ❤️ Спасибо авторам за отличные инструменты. + +## Смена сервера у клиента +См. [docs/SERVER-SWITCH.md](docs/SERVER-SWITCH.md): какие приложения переключаются сами (роутер, iOS, Android, Windows) и что делать в остальных. diff --git a/VERSION b/VERSION index 079d7f6..bc80560 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.11 +1.5.0 diff --git a/android-client/AndroidManifest.xml b/android-client/AndroidManifest.xml index 22b461f..fc02dca 100644 --- a/android-client/AndroidManifest.xml +++ b/android-client/AndroidManifest.xml @@ -1,15 +1,17 @@ + android:versionCode="147" + android:versionName="1.4.6"> + + + + + + + + + + + + diff --git a/android-client/src/com/pca/phobos/vpn/MainActivity.java b/android-client/src/com/pca/phobos/vpn/MainActivity.java index ccc3739..be5db15 100644 --- a/android-client/src/com/pca/phobos/vpn/MainActivity.java +++ b/android-client/src/com/pca/phobos/vpn/MainActivity.java @@ -56,6 +56,7 @@ import java.util.concurrent.Executors; */ public class MainActivity extends Activity { static final int LPORT = 13255; + static final String ACTION_TOGGLE = "com.pca.phobos.vpn.TOGGLE"; static final String MODE_ALL = "all", MODE_ONLY = "only", MODE_EXCEPT = "except"; static GoBackend backend; @@ -63,6 +64,15 @@ public class MainActivity extends Activity { static final int TCP_LPORT = 13256; static volatile String transport = ""; // "UDP" | "TCP" | "Облако" while up static WsRelay wsRelay; + static V6Relay v6Relay; + static final int V6_LPORT = 13257; + static final Object LOCK = new Object(); + /** Panel subscription (# pca-sub): servers in the panel's order, IPv6 entries last; failover walks them round-robin. */ + static org.json.JSONArray sub = new org.json.JSONArray(); + static volatile int subIndex; + static volatile String curConf = ""; // profile pointed at the server in use + static volatile long watchGen; // bumped on connect/disconnect: stops the old watcher + static final long SILENCE_MS = 40000; // no server reply this long -> next server static final Tunnel TUNNEL = new Tunnel() { public String getName() { return "phobos"; } public void onStateChange(Tunnel.State s) { } @@ -72,7 +82,7 @@ public class MainActivity extends Activity { private final Handler ui = new Handler(Looper.getMainLooper()); private SharedPreferences prefs; private boolean dark; - private TextView status, traffic; + private TextView status, traffic, server; private Button power; private long lastRx, lastTx, lastT; @@ -82,6 +92,9 @@ public class MainActivity extends Activity { prefs = getSharedPreferences("vpn", MODE_PRIVATE); dark = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; if (backend == null) backend = new GoBackend(getApplicationContext()); + if (android.os.Build.VERSION.SDK_INT >= 33 && checkSelfPermission("android.permission.POST_NOTIFICATIONS") != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{"android.permission.POST_NOTIFICATIONS"}, 8); + } handleIntent(getIntent()); render(); } @@ -94,6 +107,14 @@ public class MainActivity extends Activity { } private void handleIntent(Intent i) { + if (i != null && ACTION_TOGGLE.equals(i.getAction())) { + setIntent(new Intent()); + // tile: toggle and get out of the way, unless Android must ask for the VPN permission first + if (!isUp() && !prefs.getString("conf", "").isEmpty() && VpnService.prepare(this) == null) { bg.execute(this::up); finish(); return; } + if (isUp()) { bg.execute(this::down); finish(); return; } + toggle(); + return; + } if (i != null && i.getData() != null && "phobos".equals(i.getData().getScheme())) { importLink(i.getDataString()); } @@ -140,16 +161,21 @@ public class MainActivity extends Activity { // ── profile ─────────────────────────────────────────────────────────── /** phobos://#name (format produced by the PCA Phobos panel). */ - private void importLink(String link) { + private void importLink(String link) { importText(link, "Phobos"); } + + /** A phobos:// link, or the text of a client .conf ([Interface] + [Peer] + [instance]). */ + private void importText(String text, String name) { try { - String s = link.trim(); - String name = "Phobos"; - int hash = s.indexOf('#'); - if (hash > 0) { name = Uri.decode(s.substring(hash + 1)); s = s.substring(0, hash); } - s = s.replaceFirst("^phobos://", "").trim(); - String conf = new String(Base64.decode(s, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING), StandardCharsets.UTF_8); - if (!conf.contains("[Interface]") || !conf.contains("[instance]")) throw new IllegalArgumentException("not a Phobos config"); - Config.parse(new BufferedReader(new StringReader(wgPart(conf, MODE_ALL, "")))); // validate + String s = text.replace("\uFEFF", "").trim(), conf = s; + if (s.startsWith("phobos://")) { + int hash = s.indexOf('#'); + if (hash > 0) { name = Uri.decode(s.substring(hash + 1)); s = s.substring(0, hash); } + s = s.replaceFirst("^phobos://", "").trim(); + conf = new String(Base64.decode(s, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING), StandardCharsets.UTF_8); + } + if (!conf.contains("[Interface]") || !conf.contains("[instance]")) + throw new IllegalArgumentException("нужен конфиг Phobos: секции [Interface], [Peer] и [instance]"); + Config.parse(new BufferedReader(new StringReader(wgPart(conf, MODE_ALL, "", false)))); // validate prefs.edit().putString("conf", conf).putString("name", name).apply(); toast("Профиль «" + name + "» импортирован"); } catch (Exception e) { @@ -174,13 +200,15 @@ public class MainActivity extends Activity { private static String key(String line) { return line.substring(0, line.indexOf('=')).trim().toLowerCase(); } private static String val(String line) { return line.substring(line.indexOf('=') + 1).trim(); } - String wgPart(String conf, String mode, String apps) { + String wgPart(String conf, String mode, String apps, boolean ruRoutes) { StringBuilder sb = new StringBuilder("[Interface]\n"); for (String ln : section(conf, "[Interface]")) { String k = key(ln); if (k.equals("excludedapplications") || k.equals("includedapplications")) continue; + if (k.equals("dns") && !myDns().isEmpty()) continue; sb.append(ln).append('\n'); } + if (!myDns().isEmpty()) sb.append("DNS = ").append(myDns()).append('\n'); List sel = new ArrayList<>(); for (String a : apps.split(",")) if (!a.trim().isEmpty()) sel.add(a.trim()); if (MODE_ONLY.equals(mode) && !sel.isEmpty()) { @@ -193,7 +221,7 @@ public class MainActivity extends Activity { sb.append("ExcludedApplications = ").append(String.join(", ", ex)).append('\n'); } sb.append("\n[Peer]\n"); - String ruBypass = prefs != null && prefs.getBoolean("ru_direct", true) ? ruBypassAllowedIps() : null; + String ruBypass = ruRoutes && prefs.getBoolean("ru_direct", true) ? ruBypassAllowedIps() : null; for (String ln : section(conf, "[Peer]")) { if (key(ln).equals("endpoint")) ln = "Endpoint = 127.0.0.1:" + LPORT; if (key(ln).equals("allowedips") && ruBypass != null) ln = "AllowedIPs = " + ruBypass; @@ -202,33 +230,40 @@ public class MainActivity extends Activity { return sb.toString(); } - private String ruCache; - - /** Internet minus Russian networks (assets/ru_bypass.txt, generated from ipdeny). */ + /** Internet minus Russian networks (assets/ru_nets.txt) and the user's own sites; resolves domains, so not on the UI thread. */ private String ruBypassAllowedIps() { - if (ruCache != null) return ruCache; + List ru = new ArrayList<>(); try (BufferedReader r = new BufferedReader(new java.io.InputStreamReader( - getAssets().open("ru_bypass.txt"), StandardCharsets.UTF_8))) { + getAssets().open("ru_nets.txt"), StandardCharsets.UTF_8))) { for (String ln; (ln = r.readLine()) != null; ) { - if (!ln.startsWith("#") && ln.contains("/")) { ruCache = ln.trim(); break; } + if (!ln.startsWith("#") && ln.contains("/")) for (String c : ln.split(",")) ru.add(c.trim()); } - } catch (Exception e) { log("ru_bypass: " + e); } - return ruCache; + } catch (Exception e) { log("ru_nets: " + e); } + return RuNets.allowed(ru, lines(prefs.getString("my_direct", "")), lines(prefs.getString("my_vpn", ""))); } + private static List lines(String s) { + List out = new ArrayList<>(); + for (String l : s.split("[\\n,\\s]+")) if (!l.trim().isEmpty()) out.add(l.trim()); + return out; + } + + /** User's own DNS servers ("1.1.1.1, 8.8.8.8"); empty = the profile's. */ + private String myDns() { return prefs.getString("dns", ""); } + private static String getPackageNameSafe() { return "com.pca.phobos.vpn"; } private static final java.util.Set OBF_KEYS = new java.util.HashSet<>(java.util.Arrays.asList( "target", "key", "masking", "idle-timeout", "max-dummy", "static-bindings", "max-clients", "target-if", "allow-clean", "in-timeout", "resolve-interval")); - private String obfPart(String conf, boolean viaRelay) { + private String obfPart(String conf, String targetOverride) { StringBuilder sb = new StringBuilder("[instance]\nsource-if = 127.0.0.1\nsource-lport = " + LPORT + "\n"); for (String ln : section(conf, "[instance]")) { String k = key(ln); if (k.equals("source-if") || k.equals("source-lport") || k.equals("verbose")) continue; // configs from other Phobos builds carry keys our wg-obfuscator v1.6 rejects (obfuscate-bytes…) if (!OBF_KEYS.contains(k)) continue; - if (viaRelay && k.equals("target")) ln = "target = 127.0.0.1:" + TCP_LPORT; + if (targetOverride != null && k.equals("target")) ln = "target = " + targetOverride; sb.append(ln).append('\n'); } sb.append("verbose = INFO\n"); @@ -255,14 +290,88 @@ public class MainActivity extends Activity { return ""; } + /** "# pca-sub = http://panel/api/phone-sub/?token=…" */ + private static String subUrl(String conf) { + for (String ln : conf.split("\n")) { + String t = ln.trim(); + if (t.startsWith("#") && t.contains("pca-sub") && t.contains("=")) return val(t); + } + return ""; + } + + /** Same profile pointed at another subscription server (address, obfuscator key, server WG key, TCP port). */ + static String applying(String conf, org.json.JSONObject s) { + String host = s.optString("host"), sec = ""; + if (host.contains(":")) host = "[" + host + "]"; + StringBuilder sb = new StringBuilder(); + for (String raw : conf.split("\n", -1)) { + String ln = raw.trim(); + if (ln.startsWith("[") && ln.endsWith("]")) sec = ln.toLowerCase(); + else if (ln.startsWith("#")) { if (ln.contains("pca-tcp")) continue; } + else if (ln.contains("=")) { + String k = key(ln); + if (sec.equals("[instance]") && k.equals("target")) { sb.append("target = ").append(host).append(':').append(s.optInt("port")).append('\n'); continue; } + if (sec.equals("[instance]") && k.equals("key")) { sb.append("key = ").append(s.optString("key")).append('\n'); continue; } + if (sec.equals("[peer]") && k.equals("publickey")) { sb.append("PublicKey = ").append(s.optString("wg_pub")).append('\n'); continue; } + } + sb.append(raw).append('\n'); + } + if (s.optInt("tcp") != 0) sb.append("# pca-tcp = ").append(s.optInt("tcp")).append('\n'); + return sb.toString(); + } + + /** Pull the server list from the panel (the app is outside the tunnel); cached when the panel is unreachable. */ + private void refreshSub(String conf) { + String u = subUrl(conf); + if (u.isEmpty()) { sub = new org.json.JSONArray(); return; } + try { + java.net.HttpURLConnection c = (java.net.HttpURLConnection) new java.net.URL(u).openConnection(); + c.setConnectTimeout(6000); c.setReadTimeout(6000); + try (java.io.InputStream in = c.getInputStream()) { + String body = new String(readAll(in), StandardCharsets.UTF_8); + org.json.JSONArray a = new org.json.JSONObject(body).getJSONArray("servers"); + if (a.length() > 0) prefs.edit().putString("sub", a.toString()).apply(); + } + } catch (Exception e) { log("sub: " + e); } + try { sub = new org.json.JSONArray(prefs.getString("sub", "[]")); } catch (Exception e) { sub = new org.json.JSONArray(); } + } + + private static byte[] readAll(java.io.InputStream in) throws java.io.IOException { + java.io.ByteArrayOutputStream o = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + for (int n; (n = in.read(buf)) > 0; ) o.write(buf, 0, n); + return o.toByteArray(); + } + private String target(String conf) { for (String ln : section(conf, "[instance]")) if (key(ln).equals("target")) return val(ln); return "?"; } // ── connect / disconnect ────────────────────────────────────────────── - private boolean isUp() { - try { return backend.getState(TUNNEL) == Tunnel.State.UP; } catch (Exception e) { return false; } + private boolean isUp() { return tunnelUp(); } + + static boolean tunnelUp() { + try { return backend != null && backend.getState(TUNNEL) == Tunnel.State.UP; } catch (Exception e) { return false; } + } + + /** Ongoing notification = shield in the status bar while the VPN is on (GoBackend's service has none). */ + private void notifyState(boolean up) { + android.app.NotificationManager nm = (android.app.NotificationManager) getSystemService(NOTIFICATION_SERVICE); + if (nm == null) return; + if (!up) { nm.cancel(1); PhobosTile.refresh(this); return; } + if (android.os.Build.VERSION.SDK_INT >= 26) { + nm.createNotificationChannel(new android.app.NotificationChannel("vpn", "VPN", android.app.NotificationManager.IMPORTANCE_LOW)); + } + android.app.PendingIntent open = android.app.PendingIntent.getActivity(this, 0, + new Intent(this, MainActivity.class), android.app.PendingIntent.FLAG_IMMUTABLE); + android.app.Notification.Builder b = android.os.Build.VERSION.SDK_INT >= 26 + ? new android.app.Notification.Builder(this, "vpn") : new android.app.Notification.Builder(this); + b.setSmallIcon(R.drawable.ic_vpn).setContentTitle("VPN") + .setContentText("Сервер: " + target(curConf.isEmpty() ? prefs.getString("conf", "") : curConf)) + .setOngoing(true).setContentIntent(open); + try { nm.notify(1, b.build()); } catch (SecurityException ignored) { } + PhobosTile.refresh(this); } private void toggle() { @@ -275,6 +384,20 @@ public class MainActivity extends Activity { @Override protected void onActivityResult(int req, int res, Intent data) { + if (req == 9) { + if (res != RESULT_OK || data == null || data.getData() == null) return; + String name = "Phobos"; + try (android.database.Cursor c = getContentResolver().query(data.getData(), null, null, null, null)) { + int i = c == null ? -1 : c.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME); + if (i >= 0 && c.moveToFirst()) name = c.getString(i).replaceFirst("\\.[^.]*$", ""); + } catch (Exception ignored) { } + try (java.io.InputStream in = getContentResolver().openInputStream(data.getData())) { + importText(new String(readAll(in), StandardCharsets.UTF_8), name); + } catch (Exception e) { toast("Не удалось прочитать файл: " + e.getMessage()); } + applyIfUp(); + render(); + return; + } if (req == 7 && res == RESULT_OK) bg.execute(this::up); else if (req == 7) toast("Без разрешения на VPN подключиться нельзя"); } @@ -289,13 +412,19 @@ public class MainActivity extends Activity { wsRelay = new WsRelay(TCP_LPORT, wsUrl(conf), this::log); wsRelay.start(); } + String target = target(conf), host = target.replaceAll(":\\d+$", "").replaceAll("[\\[\\]]", ""); + String override = viaTcp || viaWs ? "127.0.0.1:" + TCP_LPORT : null; + if (!viaTcp && !viaWs && host.contains(":")) { + v6Relay = new V6Relay(V6_LPORT, host, Integer.parseInt(target.replaceAll("^.*:", ""))); + v6Relay.start(); + override = "127.0.0.1:" + V6_LPORT; + } if (viaTcp) { - String host = target(conf).replaceAll(":\\d+$", ""); relay = new ProcessBuilder(lib + "/libudptcp.so", "-c", "127.0.0.1:" + TCP_LPORT, host + ":" + tcpPort(conf)) .redirectErrorStream(true).redirectOutput(new File(getFilesDir(), "tcp.log")).start(); } File cfg = new File(getFilesDir(), "obf.conf"); - try (FileOutputStream o = new FileOutputStream(cfg)) { o.write(obfPart(conf, viaTcp || viaWs).getBytes(StandardCharsets.UTF_8)); } + try (FileOutputStream o = new FileOutputStream(cfg)) { o.write(obfPart(conf, override).getBytes(StandardCharsets.UTF_8)); } obfuscator = new ProcessBuilder(lib + "/libwgobfuscator.so", "-c", cfg.getAbsolutePath()) .redirectErrorStream(true).redirectOutput(new File(getFilesDir(), "obf.log")).start(); Thread.sleep(600); @@ -306,32 +435,74 @@ public class MainActivity extends Activity { } private void up() { - try { - String conf = prefs.getString("conf", ""); - String mode = prefs.getString("transport", "auto"); - boolean tcpAvail = !tcpPort(conf).isEmpty(); - if ("tcp".equals(mode) && !tcpAvail) throw new IllegalStateException("на сервере нет TCP-канала — обновите панель и заново импортируйте ссылку"); - startTransport(conf, "auto".equals(mode) ? "udp" : mode); - Config c = Config.parse(new BufferedReader(new StringReader( - wgPart(conf, prefs.getString("mode", MODE_ALL), prefs.getString("apps", ""))))); - backend.setState(TUNNEL, Tunnel.State.UP, c); - log("up ok, mode " + prefs.getString("mode", MODE_ALL) + ", transport " + mode); - post("Подключено (" + transport + ")"); - if ("auto".equals(mode) && tcpAvail) watchUdp(conf); - } catch (Throwable e) { - stopObfuscator(); - log("up failed: " + e); - post("Ошибка: " + e.getMessage()); + synchronized (LOCK) { + try { + String base = prefs.getString("conf", ""); + refreshSub(base); + subIndex = 0; + connect(sub.length() > 0 ? applying(base, sub.getJSONObject(0)) : base); + log("up ok, mode " + prefs.getString("mode", MODE_ALL) + ", servers " + Math.max(1, sub.length())); + notifyState(true); + post("Подключено (" + transport + ")"); + if (sub.length() > 1 && !"cloud".equals(prefs.getString("transport", "auto"))) watchServer(base); + } catch (Throwable e) { + stopObfuscator(); + log("up failed: " + e); + post("Ошибка: " + e.getMessage()); + } } } + /** Obfuscator (+ relay) and WireGuard for one server; called again in place on failover. */ + private void connect(String conf) throws Exception { + String mode = prefs.getString("transport", "auto"); + boolean tcpAvail = !tcpPort(conf).isEmpty(); + if ("tcp".equals(mode) && !tcpAvail && sub.length() == 0) throw new IllegalStateException("на сервере нет TCP-канала — обновите панель и заново импортируйте ссылку"); + startTransport(conf, "tcp".equals(mode) && !tcpAvail ? "udp" : "auto".equals(mode) ? "udp" : mode); + Config c = Config.parse(new BufferedReader(new StringReader( + wgPart(conf, prefs.getString("mode", MODE_ALL), prefs.getString("apps", ""), true)))); + backend.setState(TUNNEL, Tunnel.State.UP, c); + curConf = conf; + if ("auto".equals(mode) && tcpAvail) watchUdp(conf); + } + + /** Server answered nothing for SILENCE_MS -> next subscription server. Runs while the VPN service keeps the process alive. */ + private void watchServer(String base) { + final long gen = ++watchGen; + new Thread(() -> { + long lastRx = -1, since = System.currentTimeMillis(); + while (gen == watchGen) { + try { Thread.sleep(5000); } catch (InterruptedException e) { return; } + if (gen != watchGen || !isUp()) return; + long rx; + try { rx = backend.getStatistics(TUNNEL).totalRx(); } catch (Exception e) { continue; } + long now = System.currentTimeMillis(); + if (rx != lastRx) { lastRx = rx; since = now; continue; } + if (now - since < SILENCE_MS) continue; + since = now; + synchronized (LOCK) { + if (gen != watchGen) return; + try { + subIndex = (subIndex + 1) % sub.length(); + org.json.JSONObject s = sub.getJSONObject(subIndex); + log("server silent -> " + s.optString("host") + ":" + s.optInt("port")); + connect(applying(base, s)); + notifyState(true); + post("Сервер не отвечал — перешёл на " + target(curConf)); + } catch (Throwable e) { log("failover: " + e); } + } + } + }, "phobos-watch").start(); + } + /** Auto: no bytes back over UDP within 15 s -> UDP is cut, move the obfuscator to TCP. */ private void watchUdp(String conf) { new Thread(() -> { try { + long rx0 = backend.getStatistics(TUNNEL).totalRx(); Thread.sleep(15000); - if (!isUp() || !"UDP".equals(transport)) return; - if (backend.getStatistics(TUNNEL).totalRx() > 0) return; + if (!isUp() || !"UDP".equals(transport) || !conf.equals(curConf)) return; + if (backend.getStatistics(TUNNEL).totalRx() > rx0) return; log("UDP: no answer in 15 s -> switching to TCP"); startTransport(conf, "tcp"); post("UDP не отвечает — переключился на TCP"); @@ -340,9 +511,12 @@ public class MainActivity extends Activity { } private void down() { + watchGen++; + curConf = ""; try { backend.setState(TUNNEL, Tunnel.State.DOWN, null); } catch (Throwable ignored) { } stopObfuscator(); transport = ""; + notifyState(false); post("Отключено"); } @@ -350,6 +524,7 @@ public class MainActivity extends Activity { if (obfuscator != null) { obfuscator.destroy(); obfuscator = null; } if (relay != null) { relay.destroy(); relay = null; } if (wsRelay != null) { wsRelay.stop(); wsRelay = null; } + if (v6Relay != null) { v6Relay.stop(); v6Relay = null; } } private void post(String msg) { ui.post(() -> { toast(msg); refresh(); }); } @@ -383,7 +558,8 @@ public class MainActivity extends Activity { + "на этом устройстве, или скопируйте её и нажмите «Импорт».", muted(), 14, false)); } else { prof.addView(tv(prefs.getString("name", "Phobos"), text(), 18, true)); - prof.addView(tv("Сервер: " + target(conf), muted(), 14, false)); + server = tv("", muted(), 14, false); + prof.addView(server); } box.addView(prof, mw(0)); @@ -448,13 +624,18 @@ public class MainActivity extends Activity { tg.addView(r); } split.addView(tg); + int ns = lines(prefs.getString("my_direct", "")).size() + lines(prefs.getString("my_vpn", "")).size(); + split.addView(button("Свои сайты (" + ns + ")", 0xFF475569, v -> sitesDialog()), mw(4)); int n = prefs.getString("apps", "").isEmpty() ? 0 : prefs.getString("apps", "").split(",").length; split.addView(button("Выбрать приложения (" + n + ")", 0xFF475569, v -> pickApps()), mw(8)); + split.addView(button("Свой DNS" + (myDns().isEmpty() ? "" : " (" + myDns() + ")"), 0xFF475569, v -> dnsDialog()), mw(8)); split.addView(tv("Изменения применяются сразу. Само приложение всегда идёт напрямую — так обфускатор держит связь с сервером.", muted(), 12, false), mw(6)); box.addView(split, mw(20)); box.addView(button("Импорт ссылки phobos://", 0xFF4F46E5, v -> importDialog()), mw(16)); + box.addView(button("Импорт из файла .conf", 0xFF4F46E5, v -> startActivityForResult( + new Intent(Intent.ACTION_OPEN_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE).setType("*/*"), 9)), mw(8)); box.addView(button("Проверить скорость", 0xFF0EA5E9, v -> speedtest()), mw(8)); box.addView(button("Журнал", 0xFF475569, v -> showLog()), mw(8)); root.addView(sv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1)); @@ -468,6 +649,8 @@ public class MainActivity extends Activity { if (power == null) return; boolean up = isUp(); power.setText(up ? "Отключить" : "Подключить"); + String conf = prefs.getString("conf", ""); + if (server != null && !conf.isEmpty()) server.setText("Сервер: " + target(up && !curConf.isEmpty() ? curConf : conf)); power.setBackground(round(up ? 0xFF16A34A : 0xFF4F46E5, 90)); status.setText(up ? "🟢 VPN включён" + (transport.isEmpty() ? "" : " · " + transport) : "⚪ VPN выключен"); if (up) { @@ -500,6 +683,22 @@ public class MainActivity extends Activity { .setNegativeButton("Отмена", null).show(); } + private void dnsDialog() { + final EditText e = new EditText(this); + e.setHint("1.1.1.1, 8.8.8.8"); + e.setText(myDns()); + new AlertDialog.Builder(this).setTitle("Свой DNS").setView(e) + .setMessage("Адреса DNS-серверов через запятую. Пусто — DNS из профиля.") + .setPositiveButton("Сохранить", (d, w) -> { + String v = String.join(", ", lines(e.getText().toString())); + if (!v.isEmpty() && !v.matches("[0-9a-fA-F:., ]+")) { toast("DNS — только IP-адреса, например 1.1.1.1"); return; } + prefs.edit().putString("dns", v).apply(); + applyIfUp(); + render(); + }) + .setNegativeButton("Отмена", null).show(); + } + /** Channel "Облако": address of the user's Keenetic web app (KeenDNS cloud) with wsgw behind it. */ private void wsDialog() { final EditText e = new EditText(this); @@ -516,6 +715,36 @@ public class MainActivity extends Activity { .setNegativeButton("Отмена", null).show(); } + /** User's own exceptions to "Russian sites directly": domains, IPs or networks, one per line. */ + private void sitesDialog() { + LinearLayout l = new LinearLayout(this); + l.setOrientation(LinearLayout.VERTICAL); + l.setPadding(dp(16), dp(8), dp(16), 0); + l.addView(tv("Напрямую, без VPN (по одному на строку):", text(), 14, true)); + final EditText direct = new EditText(this); + direct.setHint("mysite.ru\n1.2.3.4\n5.6.0.0/16"); + direct.setText(prefs.getString("my_direct", "")); + direct.setMinLines(3); + l.addView(direct); + l.addView(tv("Всегда через VPN, даже если российский:", text(), 14, true), mw(8)); + final EditText vpn = new EditText(this); + vpn.setHint("rutracker.org"); + vpn.setText(prefs.getString("my_vpn", "")); + vpn.setMinLines(3); + l.addView(vpn); + l.addView(tv("Работает, когда включено «Российские сайты — напрямую». Сайт берётся по его текущим адресам при подключении.", muted(), 12, false), mw(6)); + ScrollView sv = new ScrollView(this); + sv.addView(l); + new AlertDialog.Builder(this).setTitle("Свои сайты").setView(sv) + .setPositiveButton("Сохранить", (d, w) -> { + prefs.edit().putString("my_direct", direct.getText().toString().trim()) + .putString("my_vpn", vpn.getText().toString().trim()).apply(); + applyIfUp(); + render(); + }) + .setNegativeButton("Отмена", null).show(); + } + private void pickApps() { PackageManager pm = getPackageManager(); Intent main = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER); diff --git a/android-client/src/com/pca/phobos/vpn/PhobosTile.java b/android-client/src/com/pca/phobos/vpn/PhobosTile.java new file mode 100644 index 0000000..b68ab93 --- /dev/null +++ b/android-client/src/com/pca/phobos/vpn/PhobosTile.java @@ -0,0 +1,36 @@ +package com.pca.phobos.vpn; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Build; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; + +/** Quick settings tile: on/off. The connect logic lives in MainActivity, which handles ACTION_TOGGLE and closes itself. */ +public class PhobosTile extends TileService { + @Override + public void onStartListening() { update(); } + + @Override + public void onClick() { + Intent i = new Intent(this, MainActivity.class).setAction(MainActivity.ACTION_TOGGLE) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (Build.VERSION.SDK_INT >= 34) { + startActivityAndCollapse(PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE)); + } else { + startActivityAndCollapse(i); + } + } + + private void update() { + Tile t = getQsTile(); + if (t == null) return; + t.setState(MainActivity.tunnelUp() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + t.updateTile(); + } + + /** Called by MainActivity after connect/disconnect so an open shade shows the new state. */ + static void refresh(android.content.Context c) { + requestListeningState(c, new android.content.ComponentName(c, PhobosTile.class)); + } +} diff --git a/android-client/src/com/pca/phobos/vpn/RuNets.java b/android-client/src/com/pca/phobos/vpn/RuNets.java new file mode 100644 index 0000000..a072bd7 --- /dev/null +++ b/android-client/src/com/pca/phobos/vpn/RuNets.java @@ -0,0 +1,101 @@ +package com.pca.phobos.vpn; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * AllowedIPs for "Russian sites directly": the whole IPv4 internet minus the direct set, + * where direct = Russian networks + the user's "direct" entries - the user's "via VPN" entries. + * Entries: IP, CIDR or domain (resolved to its current IPv4 addresses). + */ +final class RuNets { + private RuNets() { } + + static String allowed(List ruCidrs, List direct, List viaVpn) { + List d = new ArrayList<>(); + for (String c : ruCidrs) add(d, c); + for (String e : direct) for (String c : resolve(e)) add(d, c); + List v = new ArrayList<>(); + for (String e : viaVpn) for (String c : resolve(e)) add(v, c); + List directSet = subtract(merge(d), merge(v)); + // complement over 0.0.0.0 - 255.255.255.255 + List out = new ArrayList<>(); + long next = 0; + for (long[] r : directSet) { + if (r[0] > next) cidrs(next, r[0] - 1, out); + next = Math.max(next, r[1] + 1); + } + if (next <= 0xFFFFFFFFL) cidrs(next, 0xFFFFFFFFL, out); + return String.join(", ", out); + } + + /** "1.2.3.4", "1.2.3.0/24" or "site.ru" -> CIDRs (empty if it does not resolve). */ + static List resolve(String entry) { + List out = new ArrayList<>(); + String e = entry.trim().toLowerCase().replaceFirst("^\\w+://", "").replaceFirst("/$", ""); + if (e.isEmpty() || e.startsWith("#")) return out; + if (e.matches("[0-9.]+(/\\d+)?")) { out.add(e.contains("/") ? e : e + "/32"); return out; } + e = e.replaceFirst("/.*$", ""); + try { + for (InetAddress a : InetAddress.getAllByName(e)) if (a instanceof Inet4Address) out.add(a.getHostAddress() + "/32"); + } catch (Exception ignored) { } + return out; + } + + private static void add(List list, String cidr) { + try { + String[] p = cidr.trim().split("/"); + String[] o = p[0].split("\\."); + if (o.length != 4) return; + long ip = 0; + for (String x : o) ip = (ip << 8) | Integer.parseInt(x); + int bits = p.length > 1 ? Integer.parseInt(p[1]) : 32; + long size = 1L << (32 - bits); + long start = ip & ~(size - 1) & 0xFFFFFFFFL; + list.add(new long[]{start, start + size - 1}); + } catch (Exception ignored) { } + } + + private static List merge(List in) { + Collections.sort(in, (a, b) -> Long.compare(a[0], b[0])); + List out = new ArrayList<>(); + for (long[] r : in) { + if (!out.isEmpty() && r[0] <= out.get(out.size() - 1)[1] + 1) { + long[] last = out.get(out.size() - 1); + last[1] = Math.max(last[1], r[1]); + } else out.add(new long[]{r[0], r[1]}); + } + return out; + } + + private static List subtract(List a, List b) { + List out = new ArrayList<>(); + for (long[] r : a) { + long s = r[0], e = r[1]; + for (long[] x : b) { + if (x[1] < s || x[0] > e) continue; + if (x[0] > s) out.add(new long[]{s, x[0] - 1}); + s = x[1] + 1; + if (s > e) break; + } + if (s <= e) out.add(new long[]{s, e}); + } + return out; + } + + private static void cidrs(long start, long end, List out) { + while (start <= end) { + int bits = 32; + while (bits > 0) { + long size = 1L << (33 - bits); + if ((start & (size - 1)) != 0 || start + size - 1 > end) break; + bits--; + } + out.add(((start >> 24) & 255) + "." + ((start >> 16) & 255) + "." + ((start >> 8) & 255) + "." + (start & 255) + "/" + bits); + start += 1L << (32 - bits); + } + } +} diff --git a/android-client/src/com/pca/phobos/vpn/V6Relay.java b/android-client/src/com/pca/phobos/vpn/V6Relay.java new file mode 100644 index 0000000..d6f983e --- /dev/null +++ b/android-client/src/com/pca/phobos/vpn/V6Relay.java @@ -0,0 +1,48 @@ +package com.pca.phobos.vpn; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * 127.0.0.1:lport <-> [ipv6]:port over UDP: our wg-obfuscator is IPv4-only, the server side + * listens on IPv6 through phobos-v6relay. One upstream socket, like the server's relay. + */ +final class V6Relay { + private final DatagramSocket local, up; + private volatile SocketAddress peer; + private volatile boolean running = true; + + V6Relay(int lport, String host, int port) throws Exception { + local = new DatagramSocket(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), lport)); + up = new DatagramSocket(); + up.connect(new InetSocketAddress(InetAddress.getByName(host), port)); + } + + void start() { + new Thread(() -> pump(local, true), "v6relay-out").start(); + new Thread(() -> pump(up, false), "v6relay-in").start(); + } + + private void pump(DatagramSocket from, boolean out) { + byte[] buf = new byte[65535]; + while (running) { + try { + DatagramPacket p = new DatagramPacket(buf, buf.length); + from.receive(p); + if (out) { peer = p.getSocketAddress(); up.send(new DatagramPacket(buf, p.getLength())); } + else if (peer != null) local.send(new DatagramPacket(buf, p.getLength(), peer)); + } catch (Exception e) { + if (!running) return; + } + } + } + + void stop() { + running = false; + local.close(); + up.close(); + } +} diff --git a/app.py b/app.py index 82d78a3..59a2ed7 100644 --- a/app.py +++ b/app.py @@ -8,7 +8,10 @@ import json, os, subprocess, threading, time, secrets, hashlib, re, fcntl, hmac, import html as html_lib from datetime import datetime, timedelta from pathlib import Path -from flask import Flask, request, redirect, url_for, session, make_response, send_from_directory +import urllib.request as _ur +# git.bobyrev.com sits behind Cloudflare, which answers 403 to the default "Python-urllib" User-Agent (11.11.2026) +_op = _ur.build_opener(); _op.addheaders = [("User-Agent", "PCA-Phobos-Panel")]; _ur.install_opener(_op) +from flask import Flask, request, redirect, url_for, session, make_response, send_from_directory, jsonify try: from flask_sock import Sock except Exception: @@ -37,7 +40,7 @@ WG_CONFIG = "/etc/wireguard/wg0.conf" # Distribution: Gitea first (GitHub account is unavailable since 23.10.2026), GitHub fallback PCA_GIT = "https://git.bobyrev.com/PCA/PCA_Phobos" PCA_GH_RAW = "https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos" -APK_TAG = "v1.4.4" +APK_TAG = "v1.4.10" WARP_DIR = f"{PHOBOS_DIR}/server/warp" WARP_CUSTOM_PROFILE_FILE = f"{WARP_DIR}/custom-profile.conf" WARP_LICENSE_FILE = f"{WARP_DIR}/license.key" @@ -53,6 +56,7 @@ else: f.write(app.secret_key) DEFAULT_SETTINGS = { + "client_fw": {}, "admin_pass": "OcAdmin2026!", "tg_bot_token": "", "tg_chat_id": "", @@ -73,6 +77,8 @@ DEFAULT_SETTINGS = { "max_dummy": "4", "idle": "300", }, + "obf_presets": [], + "client_presets": {}, } LOGIN_ATTEMPTS = {} LOGIN_WINDOW = 900 @@ -105,9 +111,12 @@ def save_settings(s): SRV_OBF_KEYS = ("masking", "max-dummy", "idle-timeout") -def _srv_obf_confs(): +def _srv_obf_confs(include_presets=False): import glob - return sorted(glob.glob(f"{PHOBOS_DIR}/server/wg-obfuscator-*.conf")) + confs = sorted(glob.glob(f"{PHOBOS_DIR}/server/wg-obfuscator-*.conf")) + if include_presets: + return confs + return [f for f in confs if "# preset=" not in Path(f).read_text()] def read_server_obf(): @@ -140,6 +149,158 @@ def write_server_obf(vals): return done +# ---- Obfuscator presets: extra port+key sets on the primary server, picked per client ---- +def obf_preset_for(cid): + s = load_settings() + pid = s.get("client_presets", {}).get(cid) + for p in s.get("obf_presets", []): + if p["id"] == pid: + return p + return None + + +def _preset_conf_text(p, port): + return (f"# preset={p['id']}\n[instance]\nsource-if = 0.0.0.0\nsource-lport = {port}\n" + f"target = 127.0.0.1:51820\nkey = {p['key']}\nmasking = {p['masking']}\nverbose = INFO\n" + f"idle-timeout = {p['idle']}\nmax-dummy = {p['max_dummy']}\n") + + +def apply_obf_presets(): + """Write conf+systemd unit for every preset port, start them, drop units of removed presets.""" + import glob + presets = load_settings().get("obf_presets", []) + want = {} + for p in presets: + for port in p["ports"].split(","): + want[port.strip()] = p + for f in glob.glob(f"{PHOBOS_DIR}/server/wg-obfuscator-*.conf"): + port = re.search(r"wg-obfuscator-(\d+)\.conf$", f).group(1) + if "# preset=" in Path(f).read_text() and port not in want: + subprocess.run(["systemctl", "disable", "--now", f"wg-obfuscator-{port}"], timeout=30) + os.remove(f) + try: + os.remove(f"/etc/systemd/system/wg-obfuscator-{port}.service") + except OSError: + pass + for port, p in want.items(): + Path(f"{PHOBOS_DIR}/server/wg-obfuscator-{port}.conf").write_text(_preset_conf_text(p, port)) + Path(f"/etc/systemd/system/wg-obfuscator-{port}.service").write_text( + f"[Unit]\nDescription=WireGuard Obfuscator (preset {p['name']}, port {port})\n" + "After=network.target wg-quick@wg0.service\n[Service]\nType=simple\n" + f"ExecStart=/usr/local/bin/wg-obfuscator --config {PHOBOS_DIR}/server/wg-obfuscator-{port}.conf\n" + "Restart=always\nRestartSec=5\n[Install]\nWantedBy=multi-user.target\n") + subprocess.run(["systemctl", "daemon-reload"], timeout=30) + for port in want: + subprocess.run(["systemctl", "enable", f"wg-obfuscator-{port}"], timeout=30, capture_output=True) + subprocess.run(["systemctl", "restart", f"wg-obfuscator-{port}"], timeout=30) + + +def add_obf_preset(name, ports, masking, max_dummy, idle): + """Returns (ok, message). Ports must be free and not used by the panel, WG, defaults or other presets.""" + s = load_settings() + name = (name or "").strip()[:32] + if not re.match(r"^[\w .-]+$", name): + return False, "Имя пресета: буквы, цифры, пробел, . _ -" + try: + plist = [int(x) for x in re.split(r"[,\s]+", ports.strip()) if x] + masking = masking.upper() if masking.upper() in ("AUTO", "STUN", "NONE") else "AUTO" + max_dummy = str(min(255, max(0, int(max_dummy or 4)))) + idle = str(min(3600, max(10, int(idle or 300)))) + except ValueError: + return False, "Порты и числа — только цифры." + if not plist or any(not 1024 <= x <= 65535 for x in plist): + return False, "Порты: 1024–65535." + env = _load_server_env() + busy = {int(x) for x in re.split(r"[,\s]+", env.get("OBFUSCATOR_PORTS", "")) if x.isdigit()} + busy |= {51820, int(panel_port()), int(env.get("TCP_PORT") or 0)} + for p in s["obf_presets"]: + busy |= {int(x) for x in p["ports"].split(",")} + if busy & set(plist): + return False, "Порт занят: " + ",".join(str(x) for x in sorted(busy & set(plist))) + s["obf_presets"].append({"id": secrets.token_hex(3), "name": name, "ports": ",".join(map(str, plist)), + "key": secrets.token_urlsafe(24)[:32].replace("-", "x").replace("_", "y"), + "masking": masking, "max_dummy": max_dummy, "idle": idle}) + save_settings(s) + apply_obf_presets() + return True, "Пресет добавлен, порты запущены." + + +def del_obf_preset(pid): + s = load_settings() + s["obf_presets"] = [p for p in s["obf_presets"] if p["id"] != pid] + moved = [c for c, v in s.get("client_presets", {}).items() if v == pid] + for c in moved: + s["client_presets"].pop(c, None) + save_settings(s) + apply_obf_presets() + for c in moved: + try: + fanout_router_config(c) + except Exception: + pass + return len(moved) + + +# ---- Per-client firewall: allow-list of destinations, everything else from that client is dropped ---- +FW_CHAIN = "PHOBOS_CLIENTS" + + +def parse_fw_list(text): + """'1.1.1.1, 10.0.0.0/8, 8.8.8.8:53/udp, 93.184.216.34:443' -> [(net, port|None, proto|None)]. Raises ValueError.""" + import ipaddress + out = [] + for raw in re.split(r"[,\s]+", (text or "").strip()): + if not raw: + continue + proto = None + m = re.match(r"^(.*)/(tcp|udp)$", raw) + if m: + raw, proto = m.groups() + port = None + if ":" in raw: + raw, _, ps = raw.partition(":") + if not ps.isdigit() or not 1 <= int(ps) <= 65535: + raise ValueError(raw + ":" + ps) + port = int(ps) + if (port or proto) and proto is None: + proto = "tcp" + net = ipaddress.ip_network(raw, strict=False) + if net.version != 4: + raise ValueError(raw + " (IPv4 only)") + out.append((str(net), port, proto)) + return out + + +def apply_client_fw(): + """Rebuild the PHOBOS_CLIENTS chain from settings; clients without a list stay unrestricted.""" + fw = load_settings().get("client_fw", {}) + ips = {c.get("client_id"): (c.get("tunnel_ip_v4") or "").split("/")[0] for c in get_clients()} + + def ipt(*a): + return subprocess.run(["iptables", *a], capture_output=True, timeout=15).returncode + + ipt("-N", FW_CHAIN) + ipt("-F", FW_CHAIN) + for cid, text in fw.items(): + src = ips.get(cid) + if not src or not text.strip(): + continue + try: + entries = parse_fw_list(text) + except ValueError: + continue + for net, port, proto in entries: + rule = ["-A", FW_CHAIN, "-s", src, "-d", net] + if proto: + rule += ["-p", proto] + if port: + rule += ["--dport", str(port)] + ipt(*rule, "-j", "ACCEPT") + ipt("-A", FW_CHAIN, "-s", src, "-j", "DROP") + if ipt("-C", "FORWARD", "-i", "wg0", "-j", FW_CHAIN) != 0: + ipt("-I", "FORWARD", "1", "-i", "wg0", "-j", FW_CHAIN) + + def _load_server_env(): """Load server.env as dict for subprocess env.""" env = {} @@ -378,6 +539,7 @@ def delete_client_cli(cid): try: st = load_settings() st.get("client_assignments", {}).pop(cid, None) + st.get("client_pinned", {}).pop(cid, None) st.get("client_notify", {}).pop(cid, None) save_settings(st) except Exception: @@ -1006,9 +1168,12 @@ def generate_failover_conf_for_client(client_id): ordered = [] if assigned_ip in by_ip: ordered.append(by_ip[assigned_ip]) - for sv in all_servers: - if sv["ip"] != assigned_ip: - ordered.append(sv) + # 02.11.2026: «Только этот сервер» — учётка без перехода (несколько учёток на + # одном роутере = несколько выходов: каждая закреплена за своей страной) + if not (s.get("client_pinned", {}).get(client_id) and ordered): + for sv in all_servers: + if sv["ip"] != assigned_ip: + ordered.append(sv) # Get main server WG public key try: @@ -1016,24 +1181,90 @@ def generate_failover_conf_for_client(client_id): except Exception: main_wg_pub = "" + tcp_port = str(_load_server_env().get("TCP_PORT", "") or "").strip() lines = ["# Phobos Failover Configuration"] - for i, srv in enumerate(ordered, 1): - lines.append(f"SERVER_{i}={client_endpoint(srv['ip'])}:{srv.get('ports', '2083,5443,993')}") - lines.append(f"KEY_{i}={srv.get('obfuscator_key', '')}") + i = 0 + for srv in ordered: + pr = obf_preset_for(client_id) if srv.get("is_primary") else None wg_pub = main_wg_pub if srv.get("is_primary") else srv.get("wg_public_key", "") - if wg_pub: - lines.append(f"WGKEY_{i}={wg_pub}") + # every address of the server = its own entry: router hops address × port, then TCP + for host in [client_endpoint(srv["ip"])] + alt_endpoints(srv["ip"]): + i += 1 + lines.append(f"SERVER_{i}={host}:{pr['ports'] if pr else srv.get('ports', '2083,5443,993')}") + lines.append(f"KEY_{i}={pr['key'] if pr else srv.get('obfuscator_key', '')}") + if wg_pub: + lines.append(f"WGKEY_{i}={wg_pub}") + tp = tcp_port if srv.get("is_primary") else str(srv.get("tcp_port", "") or "") + if tp.isdigit() and int(tp) > 0: + lines.append(f"TCP_{i}={tp}") # Obfuscator params (delivered live; router applies to wg-obfuscator.conf) - obf = s.get("obf_params", {}) + obf = obf_preset_for(client_id) or s.get("obf_params", {}) masking = (obf.get("masking") or "AUTO").upper() if masking not in ("AUTO", "STUN", "NONE"): masking = "AUTO" + # extra public panel URLs (alt IPs of this server): pull falls back to them when the + # tunnel and the main IP are both cut (09.33) + _purls = [f"http://{h}:{panel_port()}" for h in alt_endpoints(SERVER_IP)] + if _purls: + lines.append("PANEL_URLS_EXTRA=" + " ".join(_purls)) lines.append(f"OBF_MASKING={masking}") lines.append(f"OBF_MAX_DUMMY={obf.get('max_dummy', '4')}") lines.append(f"OBF_IDLE={obf.get('idle', '300')}") return "\n".join(lines) + "\n" +def phone_sub_token(cid): + """Stateless per-client token for the phone subscription (HMAC of the panel key).""" + import hmac, hashlib + key = (load_settings().get("server_api_key") or "").encode() + return hmac.new(key, ("phonesub:" + cid).encode(), hashlib.sha256).hexdigest()[:32] + + +def phone_sub_servers(cid): + """Ordered server list for a phone: the assigned server first (same order as failover.conf).""" + s = load_settings() + assigned = s.get("client_assignments", {}).get(cid, SERVER_IP) + allv = get_all_servers_ordered() + ordered = [sv for sv in allv if sv["ip"] == assigned] + if not (s.get("client_pinned", {}).get(cid) and ordered): + ordered += [sv for sv in allv if sv["ip"] != assigned] + try: + main_wg_pub = subprocess.check_output(["wg", "show", "wg0", "public-key"], text=True, timeout=5).strip() + except Exception: + main_wg_pub = "" + tcp_port = _load_server_env().get("TCP_PORT", "") + out = [] + for sv in ordered: + prim = bool(sv.get("is_primary")) + pr = obf_preset_for(cid) if prim else None + ports = (pr["ports"] if pr else sv.get("ports", "2083,5443,993")) + out.append({ + "host": client_endpoint(sv["ip"]), + "port": int(str(ports).split(",")[0]), + "key": pr["key"] if pr else sv.get("obfuscator_key", ""), + "wg_pub": main_wg_pub if prim else sv.get("wg_public_key", ""), + "tcp": int(tcp_port) if (prim and str(tcp_port).isdigit()) else 0, + }) + # IPv6 entries go last: phones whose operator cuts IPv4 to the server still get in. + # The server side listens via phobos-v6relay (the obfuscator itself is IPv4-only). + for sv, o in zip(ordered, list(out)): + v6 = (s.get("ipv6") if sv.get("is_primary") else sv.get("ipv6")) or "" + if v6: + out.append(dict(o, host=v6, tcp=0, ipv6=True)) + return [o for o in out if o["key"] and o["wg_pub"]] + + +@app.route("/api/phone-sub/") +def phone_sub(cid): + """Phone subscription: the app pulls the server list and fails over between servers by itself.""" + import hmac + if not hmac.compare_digest(request.args.get("token", ""), phone_sub_token(cid)): + return ("forbidden", 403) + if not any(c.get("client_id") == cid for c in get_clients()): + return ("not found", 404) + return jsonify({"servers": phone_sub_servers(cid)}) + + def panel_version(): try: return open(os.path.join(PANEL_DIR, ".version")).read().strip() @@ -1152,6 +1383,47 @@ def run_update(arg): return prefix + "update error: " + str(e) +UPDATE_LOG = "/tmp/phobos-update.log" + + +def start_update_job(arg): + """Run the updater detached from the panel (it restarts the panel at the end). Returns (ok, message).""" + if not re.match(r"^(--check|--rollback|[A-Za-z0-9._/-]{1,40})$", arg): + return False, "bad target" + st = update_job_status() + if st["running"]: + return False, "update already running" + runner = _find_update_runner() + if not runner: + ok, note = _restore_update_script(_current_update_ref(arg)) + runner = _find_update_runner() if ok else None + if not runner: + return False, f"update helper not found ({UPDATE_SCRIPT})" + Path(UPDATE_LOG).write_text(f"__START__ {arg}\n") + cmd = f'"{runner}" "{arg}" >> {UPDATE_LOG} 2>&1; echo "__EXIT__ $?" >> {UPDATE_LOG}' + try: + if shutil.which("systemd-run"): + subprocess.run(["systemd-run", "--collect", f"--unit=phobos-update-{int(time.time())}", "bash", "-c", cmd], + capture_output=True, timeout=15, check=True) + else: + subprocess.Popen(["bash", "-c", cmd], start_new_session=True) + except Exception as e: + return False, "start failed: " + str(e)[:200] + return True, "started" + + +def update_job_status(): + try: + txt = Path(UPDATE_LOG).read_text(errors="replace") + age = time.time() - os.path.getmtime(UPDATE_LOG) + except Exception: + return {"running": False, "started": False, "log": "", "rc": None, "version": panel_version()} + m = re.search(r"__EXIT__ (\d+)", txt) + running = (not m) and age < 600 + return {"running": running, "started": "__START__" in txt, "rc": int(m.group(1)) if m else None, + "log": re.sub(r"__(START|EXIT)__[^\n]*\n?", "", txt)[-3000:], "version": panel_version()} + + def warp_status(): try: r = subprocess.run( @@ -1188,6 +1460,13 @@ def script_status(name): return {} +def alt_endpoints(ip): + """Backup hosts for server `ip` (second IP, domains, cascade entries): the router + tries each of them on every port before moving to the next server (09.33).""" + v = load_settings().get("alt_endpoints", {}).get(ip, []) + return [x for x in v if isinstance(x, str) and x.strip()] + + def client_endpoint(ip): """Host clients dial for server `ip`: a domain or a cascade entry, else the IP. A host that is not a secondary's IP (old endpoint, domain) means the primary.""" @@ -1239,6 +1518,17 @@ def save_client_endpoints(form): if v and v != ip: eps[ip] = v s["endpoints"] = eps + alts = {} + for srv in get_all_servers_ordered(): + ip = srv["ip"] + items = [x.strip() for x in (form.get("alt_" + ip, "") or "").replace(";", ",").split(",") if x.strip()] + for v in items: + if not _HOST_RE.match(v): + return False, tr("Неверный адрес: ", "Bad host: ") + v + items = [v for v in dict.fromkeys(items) if v not in (ip, eps.get(ip))] + if items: + alts[ip] = items[:5] + s["alt_endpoints"] = alts save_settings(s) # new client packages read CLIENT_ENDPOINT from server.env try: @@ -1545,10 +1835,14 @@ def build_phone_config(cid, mode="android"): ("target = " + _swap_host(ln.split("=", 1)[1].strip(), client_endpoint)) if ln.strip().startswith("target") and "=" in ln else ln for ln in inst.split("\n")) + pr = obf_preset_for(cid) + if pr: + inst = _apply_preset_to_inst(inst, pr) tcp_port = _load_server_env().get("TCP_PORT", "") if tcp_port and "pca-tcp" not in inst: # comment: PhobosWG/wg-obfuscator ignore it, PCA Phobos VPN uses it for the TCP fallback inst += "\n# pca-tcp = " + tcp_port + inst += "\n# pca-sub = %s/api/phone-sub/%s?token=%s" % (panel_base_url(client_endpoint(SERVER_IP)), cid, phone_sub_token(cid)) conf = wg + "\n\n" + inst + "\n" payload = _prepare_phoboswg_payload(conf) # spec 2.1: base64url WITHOUT padding (HydraRoute rejects a trailing "=") @@ -1557,6 +1851,22 @@ def build_phone_config(cid, mode="android"): return payload, link +def _apply_preset_to_inst(inst, pr): + """Point a client's [instance] at an obfuscator preset (primary server only).""" + vals = {"key": pr["key"], "masking": pr["masking"], "idle-timeout": pr["idle"], "max-dummy": pr["max_dummy"]} + out = [] + for ln in inst.split("\n"): + k, eq, v = ln.partition("=") + k = k.strip() + if eq and k in vals: + ln = f"{k} = {vals[k]}" + elif eq and k == "target": + host = v.strip().rsplit(":", 1)[0] + ln = f"target = {host}:{pr['ports'].split(',')[0]}" + out.append(ln) + return "\n".join(out) + + def _prepare_phoboswg_payload(conf): """Normalize Android PhobosWG phobos:// payload. Do not use for raw .conf.""" mandatory = { @@ -1820,6 +2130,7 @@ def session_monitor(): if time.time() - _last_peer_sync >= 300: _last_peer_sync = time.time() sync_missing_peers_to_secondaries() + apply_client_fw() if load_settings().get("speed_limits"): apply_speed_limits() elif limits_need_reapply(): @@ -1869,6 +2180,111 @@ def finish_legacy_update(): finish_legacy_update() threading.Thread(target=rate_sampler, daemon=True).start() + +# ---- Server stats for the dashboard (Linux /proc, no extra deps) ---- +import collections, shutil +PANEL_START = time.time() +STATS_HIST = collections.deque(maxlen=150) # one sample per 2 s -> last 5 min + + +def _proc_cpu(): + v = [int(x) for x in open("/proc/stat").readline().split()[1:]] + return v[3] + (v[4] if len(v) > 4 else 0), sum(v) + + +def _meminfo(): + d = {} + for ln in open("/proc/meminfo"): + k, _, v = ln.partition(":") + d[k] = int(v.split()[0]) * 1024 + return d + + +def _iface_bytes(name): + for ln in open("/proc/net/dev"): + if ln.strip().startswith(name + ":"): + f = ln.split(":", 1)[1].split() + return int(f[0]), int(f[8]) + return 0, 0 + + +def stats_sampler(): + prev_cpu, prev_net, prev_t = _proc_cpu(), None, time.time() + while True: + time.sleep(2) + try: + now = time.time() + idle, total = _proc_cpu() + dcpu = total - prev_cpu[1] + cpu = round(100.0 * (1 - (idle - prev_cpu[0]) / dcpu), 1) if dcpu > 0 else 0.0 + prev_cpu = (idle, total) + rx, tx = _iface_bytes("wg0") + rx_bps = tx_bps = 0.0 + if prev_net is not None and now > prev_t: + rx_bps = max(0, rx - prev_net[0]) / (now - prev_t) + tx_bps = max(0, tx - prev_net[1]) / (now - prev_t) + prev_net, prev_t = (rx, tx), now + m = _meminfo() + ram_total = m.get("MemTotal", 0) + ram_used = ram_total - m.get("MemAvailable", 0) + sw_total = m.get("SwapTotal", 0) + sw_used = sw_total - m.get("SwapFree", 0) + du = os.statvfs("/") + d_total = du.f_blocks * du.f_frsize + d_used = d_total - du.f_bavail * du.f_frsize + STATS_HIST.append({"t": int(now), "cpu": cpu, "ram": ram_used, "ram_t": ram_total, + "swap": sw_used, "swap_t": sw_total, "disk": d_used, "disk_t": d_total, + "rx": round(rx_bps), "tx": round(tx_bps)}) + except Exception: + pass + + +def _proc_rss_threads(pid="self"): + rss = thr = 0 + try: + for ln in open(f"/proc/{pid}/status"): + if ln.startswith("VmRSS:"): + rss = int(ln.split()[1]) * 1024 + elif ln.startswith("Threads:"): + thr = int(ln.split()[1]) + except Exception: + pass + return rss, thr + + +def sys_info(): + obf_rss = obf_n = 0 + for pid in os.listdir("/proc"): + if pid.isdigit(): + try: + if open(f"/proc/{pid}/comm").read().strip() == "wg-obfuscator": + obf_rss += _proc_rss_threads(pid)[0] + obf_n += 1 + except Exception: + pass + rss, thr = _proc_rss_threads() + try: + os_up = float(open("/proc/uptime").read().split()[0]) + except Exception: + os_up = 0 + rx, tx = _iface_bytes("wg0") + return {"cores": os.cpu_count() or 1, "os_up": int(os_up), "panel_up": int(time.time() - PANEL_START), + "panel_rss": rss, "threads": thr, "obf_rss": obf_rss, "obf_n": obf_n, + "wg_rx": rx, "wg_tx": tx, "ip": SERVER_IP, "wg_addr": server_tunnel_addr()} + + +def server_tunnel_addr(): + try: + for ln in open("/etc/wireguard/wg0.conf"): + if ln.strip().startswith("Address"): + return ln.split("=", 1)[1].split(",")[0].strip().split("/")[0] + except Exception: + pass + return "" + + +threading.Thread(target=stats_sampler, daemon=True).start() + # ---- Monthly traffic per client, summed over all servers; resets on the 1st ---- # WireGuard counters live per server and drop to 0 on wg restart/reboot, so we add deltas. TRAFFIC_FILE = f"{PANEL_DIR}/traffic.json" @@ -1944,8 +2360,8 @@ PAGE = """ +
+SIDEBAR +
+TOPBAR

🛡️ Phobos VPN Panel

Obfuscated WireGuard · """ + SERVER_IP + """
@@ -2044,9 +2561,139 @@ CONTENT +
""" +_ICONS = { + "sessions": '', + "clients": '', + "labels": '', + "servers": '', + "settings": '', + "logout": '', +} +_SHIELD = ('') + + +def _sidebar_topbar(): + """Left menu + top bar (logged-in admin only). Returns (sidebar_html, topbar_html, body_class).""" + if not session.get("auth"): + return "", "", "noside" + path = request.path + L = cur_lang() + items = [("sessions", "/sessions", "Сессии", "Sessions"), ("clients", "/clients", "Клиенты", "Clients"), + ("labels", "/labels", "Метки", "Labels"), ("servers", "/servers", "Серверы", "Servers"), + ("settings", "/settings", "Настройки", "Settings")] + cnt = {} + try: + cnt["clients"] = len(get_clients()) + cnt["sessions"] = sum(1 for _i in get_wg_peers().values() if is_peer_online(_i.get("latest handshake", ""))) + except Exception: + pass + active = next((k for k, h, _r, _e in items if path.startswith(h)), "") + if path.startswith("/client/"): + active = "clients" + ver = html_lib.escape(panel_version().replace('-beta', 'β').replace('-dev', 'δ')) + ui = update_info() + out = ['') + other = "en" if L == "ru" else "ru" + top = (f'

{title or "Phobos"}

' + f'◐' + f'{other.upper()}
') + return "".join(out), top, "" + + +def update_hud(): + """Jarvis-style full-screen update overlay (settings page only). Intercepts update / rollback / check forms.""" + return """ + +
+ + + + + +
0%
PHOBOS
+

P H O B O S  //  UPDATE

+
+
+ +""" + + # ── «Доступна новая версия»: проверка канала раз в час + баннер с описанием ── _upd_cache = {"ts": 0, "info": None, "busy": False} @@ -2181,7 +2828,11 @@ def update_banner(): def render(content): - return PAGE.replace("CONTENT", update_banner() + content) + content = update_banner() + content + side, top, cls = _sidebar_topbar() + if request.path == "/settings" and session.get("auth"): + content = content + update_hud() + return PAGE.replace("APPCLS", cls).replace("SIDEBAR", side).replace("TOPBAR", top).replace("CONTENT", content) @app.errorhandler(404) @@ -2537,77 +3188,34 @@ def dashboard(): return redirect(url_for("sessions_page")) -@app.route("/sessions") +@app.route("/api/update/start", methods=["POST"]) @auth_required -def sessions_page(): - s = load_settings() - labels = s.get("labels", {}) - sessions = get_active_sessions() - # Online = fresh handshake on ANY server (router may be on a backup) - online_all = set() - for _pub, _info in get_wg_peers().items(): - if is_peer_online(_info.get("latest handshake", "")): - online_all.add(_pub) - for _ip, _hs in server_handshakes_cache.items(): - for _pub, _ts in _hs.items(): - if _ts and (time.time() - _ts) < 180: - online_all.add(_pub) - - rows = "" - online_count = 0 - for sess in sessions: - online = sess["public_key"] in online_all - if online: - online_count += 1 - status = 'Online' if online else 'Offline' - real_ip = sess["real_ip"] - label = labels.get(sess.get("tunnel_ip", ""), "") or labels.get(real_ip, "") - disp = client_display_name(sess["client_id"], s) - disp_html = html_lib.escape(disp) - client_cell = disp_html if disp == sess["client_id"] else f'{disp_html}
{sess["client_id"]}
' - - rows += f""" - {client_cell} - {sess['tunnel_ip']} - {real_ip} - {('' + label + '') if label else '—'} - {sess['handshake']} - {sess['rx']} / {sess['tx']} - {status} - {tr("Отключить","Kick")}{hlp("Принудительно разорвать текущую сессию клиента (сбросить WG-пир). Клиент переподключится автоматически.","Force-drop the client current session (reset the WG peer). The client reconnects automatically.")} - """ - - if not rows: - rows = '{tr("Нет активных сессий","No active sessions")}' - - html = f""" -
- {nav('sessions', online_count)} -
-

{tr("Активные сессии","Active sessions")}

- - - {rows} -
КлиентVPN IPReal IPМеткаHandshakeRX / TXСтатус
-
-
""" - return render(html) +def api_update_start(): + arg = (request.form.get("arg") or "").strip() + if arg in ("", "stable", "latest"): + arg = "main" if arg != "" else arg + ok, m = start_update_job(arg) + return jsonify({"ok": ok, "msg": m}), (200 if ok else 400) -@app.route("/kick/") +@app.route("/api/update/status") @auth_required -def kick(pub_key): - kick_peer(pub_key) - return redirect(url_for("sessions_page")) +def api_update_status(): + return jsonify(update_job_status()) -def _html_attr(value): - return html_lib.escape(str(value or ""), quote=True) +@app.route("/api/sysstats") +@auth_required +def api_sysstats(): + return jsonify({"hist": list(STATS_HIST), "info": sys_info()}) -def _terminal_button(url, title): - return (f'🖥️') +def _to_bytes(txt): + m = re.match(r"([\d.]+)\s*([KMGT]?i?B)", (txt or "").strip()) + if not m: + return 0 + mult = {"B": 1, "KiB": 1 << 10, "MiB": 1 << 20, "GiB": 1 << 30, "TiB": 1 << 40} + return float(m.group(1)) * mult.get(m.group(2), 1) def _fmt_bytes(n): @@ -2631,10 +3239,147 @@ def api_traffic(): return jsonify({"month": d.get("month"), "usage": d.get("usage", {}), "history": d.get("history", {})}) +SESS_COLORS = ["#8b8cf7", "#22d3ee", "#f472b6", "#facc15", "#34d399", "#fb923c", "#a78bfa", "#60a5fa"] + + +@app.route("/sessions") +@auth_required +def sessions_page(): + s = load_settings() + labels = s.get("labels", {}) + sessions = get_active_sessions() + # Online = fresh handshake on ANY server (router may be on a backup) + online_all = set() + for _pub, _info in get_wg_peers().items(): + if is_peer_online(_info.get("latest handshake", "")): + online_all.add(_pub) + for _ip, _hs in server_handshakes_cache.items(): + for _pub, _ts in _hs.items(): + if _ts and (time.time() - _ts) < 180: + online_all.add(_pub) + + rows = "" + online_count = 0 + seg = [] + for n, sess in enumerate(sessions): + online = sess["public_key"] in online_all + if online: + online_count += 1 + color = SESS_COLORS[n % len(SESS_COLORS)] + status = f'{tr("Онлайн","Online")}' if online else f'{tr("Оффлайн","Offline")}' + real_ip = sess["real_ip"] + label = labels.get(sess.get("tunnel_ip", ""), "") or labels.get(real_ip, "") + disp = client_display_name(sess["client_id"], s) + disp_html = html_lib.escape(disp) + sub = f'
{sess["client_id"]}
' if disp != sess["client_id"] else "" + client_cell = f'{disp_html}{sub}' + seg.append((color, _to_bytes(sess["rx"]) + _to_bytes(sess["tx"]))) + rows += f""" + {client_cell} + {sess['tunnel_ip']} + {real_ip} + {('' + html_lib.escape(label) + '') if label else '—'} + {sess['handshake']} + {sess['rx']} ↓ / {sess['tx']} ↑ + {status} + {tr("Отключить","Kick")}{hlp("Принудительно разрывает сессию клиента. Клиент переподключится сам через пару секунд, если не заблокирован.","Forcibly drops the client session. The client reconnects by itself in a few seconds unless locked.")} + """ + + if not rows: + rows = f'{tr("Нет активных сессий","No active sessions")}' + + total = sum(v for _c, v in seg) + R, C = 70, 2 * 3.14159265 * 70 + circles, off = "", 0.0 + for color, v in seg: + ln = C * (v / total) if total else 0 + if ln > 0: + circles += f'' + off += ln + donut = (f'
{circles}' + f'
{_fmt_bytes(total)}{tr("общий трафик","total traffic")}
') + + stat = lambda i, name, icon: f"""
{icon} {name}
–
 
+
""" + dash = f""" +

{tr("Сессии","Sessions")}{len(sessions)} · {tr("онлайн","online")}: {online_count}

+
{donut}
+ + {rows} +
{tr("Клиент","Client")}VPN IPReal IP{tr("Метка","Label")}{tr("Рукопожатие","Handshake")}RX / TX{tr("Статус","Status")}
+
{stat("cpu","CPU","▣")}{stat("ram","RAM","▤")}{stat("swap","SWAP","⇄")}{stat("disk",tr("ХРАНИЛИЩЕ","STORAGE"),"◫")}
+

{tr("Общая скорость","Total speed")}

+
+ +
+
{tr("Отправлено","Sent")}–
{tr("Получено","Received")}–
{tr("Среднее","Average")}–
+
+
⏱ {tr("Аптайм","Uptime")}
{tr("ПАНЕЛЬ","PANEL")}
–
{tr("ОС","OS")}
–
+
▤ {tr("Процесс панели","Panel process")}
RAM
–
{tr("ПОТОКИ","THREADS")}
–
+
▤ {tr("Обфускатор","Obfuscator")}
–
 
+
◎ {tr("IP-адреса","IP addresses")}
–
+
+ """ + html = f""" +
+ {dash} +
""" + return render(html) + + +@app.route("/kick/") +@auth_required +def kick(pub_key): + kick_peer(pub_key) + return redirect(url_for("sessions_page")) + + +def _html_attr(value): + return html_lib.escape(str(value or ""), quote=True) + + +def _terminal_button(url, title): + return (f'🖥️') + + @app.route("/clients", methods=["GET", "POST"]) @auth_required def clients_page(): s = load_settings() + # iPhone / macOS tabs only in beta/dev (stable has no Apple apps) + apple_tabs = ("iOS" + "macOS") if apple_apps() else "" subs = s.get("subscriptions", {}) msg = "" @@ -2718,6 +3463,37 @@ def clients_page(): ok, rep = apply_speed_limits() msg = f'
{html_lib.escape(rep)}
' + elif action == "set_fw": + client_id = request.form.get("client_id", "").strip() + text = request.form.get("fw_list", "").strip() + try: + parse_fw_list(text) + fwd = s.setdefault("client_fw", {}) + if text: + fwd[client_id] = text + else: + fwd.pop(client_id, None) + save_settings(s) + apply_client_fw() + msg = f'
{tr("Firewall для","Firewall for")} {html_lib.escape(client_id)}: {tr("разрешено только","allowed only")} {html_lib.escape(text) if text else tr("— ограничений нет","— no restrictions")}
' + except ValueError as e: + msg = f'
{tr("Неверная запись","Bad entry")}: {html_lib.escape(str(e))}
' + + elif action == "set_preset": + client_id = request.form.get("client_id", "").strip() + pid = request.form.get("preset_id", "").strip() + cp = s.setdefault("client_presets", {}) + if client_id and pid and any(p["id"] == pid for p in s.get("obf_presets", [])): + cp[client_id] = pid + elif client_id: + cp.pop(client_id, None) + save_settings(s) + try: + fanout_router_config(client_id) + except Exception: + pass + msg = f'
{tr("Пресет для","Preset for")} {html_lib.escape(client_id)}: {tr("сохранён; телефону — заново импортировать ссылку","saved; phones must re-import the link")}
' + elif action == "set_expiry": client_id = request.form.get("client_id", "").strip() expiry = request.form.get("expiry", "").strip() @@ -2773,6 +3549,19 @@ def clients_page(): else: msg = f'
{client_id} → {server_ip} ✓ (saved — router applies within ~15s)
' + elif action == "set_pinned": + client_id = request.form.get("client_id", "").strip() + if client_id: + pn = s.get("client_pinned", {}) + if request.form.get("pinned") == "1": + pn[client_id] = True + else: + pn.pop(client_id, None) + s["client_pinned"] = pn + save_settings(s) + fanout_router_config(client_id) + msg = f'
{client_id}: {tr("сохранено — роутер применит за ~15с", "saved — router applies within ~15s")}
' + clients = get_clients(include_orphans=True) peers = get_wg_peers() online_pubs = set() @@ -2798,7 +3587,9 @@ def clients_page(): limits = s.get("speed_limits", {}) rows = "" + modals = "" today = datetime.now().date() + _tusage = _traffic_load().get("usage", {}) for c in clients: cid = c.get("client_id", "") disp = str(aliases.get(cid, "") or "").strip() or cid @@ -2833,6 +3624,7 @@ def clients_page(): pass assigned_ip = assignments.get(cid, SERVER_IP) + pin_v = "1" if s.get("client_pinned", {}).get(cid) else "0" srv_opts = "" for sv in all_srv: sel = " selected" if sv["ip"] == assigned_ip else "" @@ -2878,6 +3670,28 @@ def clients_page(): {h_set} """ + if s.get("obf_presets"): + _cur = s.get("client_presets", {}).get(cid, "") + _opts = f'' + "".join( + f'' for p in s["obf_presets"]) + server_cell += f""" +
+ + + + +
+ """ + _fwv = s.get("client_fw", {}).get(cid, "") + server_cell += f""" +
🛡 Firewall{' ●' if _fwv else ''} +
+ + + + +
+ """ expiry_cell = f"""
@@ -2938,7 +3752,6 @@ def clients_page():
""" - _tusage = _traffic_load().get("usage", {}) if "_tusage" not in dir() else _tusage lim = str(limits.get(cid, "") or "") rate = client_rates.get(pub) speed = (f'↓{rate[0]:.1f} ↑{rate[1]:.1f} {tr("Мбит/с","Mbit/s")}' if rate else "") @@ -2950,30 +3763,124 @@ def clients_page(): {tr("Мбит/с","Mbit/s")} """ - rows += f"""
-
{client_cell} {ip} {status} {expiry_badge} {_traffic_cell(_tusage.get(cid))} {f'≤{_html_attr(lim)} Mbit' if lim and lim != "0" else ""} {speed}
-
{server_cell}{expiry_cell}{limit_cell}{rename_box}
-
{actions_cell}
-
""" + if is_orphan: + rows += f"""{html_lib.escape(disp)}
{ip}
{status} + {tr("Сиротский peer в wg0 (нет папки клиента)", "Orphan peer in wg0 (no client folder)")} +
{actions_cell}
""" + continue + primary = next((sv for sv in all_srv if sv["ip"] == assigned_ip), {}).get("is_primary") + star = ' ★' if primary else "" + name_html = f'{html_lib.escape(disp)}' + (f' ({cid})' if disp != cid else "") + sw = lambda on, action, extra="": (f'
' + f'{extra}' + f'
') + notify_sw = sw(_notify, "toggle_notify") + warp_sw = sw(warp_on, "toggle_warp_client", f'') + exp_cell = (html_lib.escape(expiry) + " " + expiry_badge) if expiry else "∞" + lim_cell = (html_lib.escape(lim) + " Mbit/s") if lim and lim != "0" else "∞" + ic = lambda d: f'{d}' + i_qr = ic('') + i_ed = ic('') + i_term = ic('') + i_del = ic('') + cur_preset = s.get("client_presets", {}).get(cid, "") + preset_row = "" + if s.get("obf_presets"): + _popts = f'' + "".join( + f'' for pp in s["obf_presets"]) + preset_row = f'' + fwv = s.get("client_fw", {}).get(cid, "") + rows += f""" + {name_html}
{ip}
+ {status} + {assigned_ip}{star} + {exp_cell} + {lim_cell} + {_traffic_cell(_tusage.get(cid))} + {notify_sw} + {warp_sw} +
+ + + +
+
""" + modals += f""" + + + """ if not rows: - rows = f'

{tr("Пока нет клиентов. Создай первого клиента выше.","No clients yet. Create first client above.")}

' + rows = f'{tr("Пока нет клиентов.","No clients yet.")}' page_html = f"""
{nav('clients')} {msg} -
-

{tr("VPN-клиенты", "VPN Clients")}

-
- - - - {hlp("Создать нового VPN-клиента (роутер/устройство). Имя — латиница, цифры, _ и -. После создания выдай команду установки на роутер.", "Create a new VPN client (router/device). Name: letters, digits, _ and -. After creation, run the install command on the router.")} -
-

⚡ {tr("Скорость на устройстве клиента (с включённым VPN)","Speed on the client device (VPN on)")}: http://{server_tunnel_ip()}:{panel_port()}/speedtest

-
{rows}
+
+
{tr("Клиенты","Clients")} {len(clients)}
+
+
+
+ + + {rows}
{tr("Клиент","Client")}{tr("Статус","Status")}{tr("Сервер","Server")} ◉{tr("Срок","Expiry")}{tr("Скорость","Speed")}{tr("Трафик за месяц","Traffic / month")}{tr("Уведомления","Notify")}WARP{tr("Действия","Actions")}
+

⚡ {tr("Скорость на устройстве клиента","Speed on the client device")}: http://{server_tunnel_addr() or "10.25.0.1"}:{panel_port()}/speedtest

-
""" + + {modals} +
+ """ return render(page_html) @@ -3120,6 +4027,16 @@ def settings_page(): except Exception: pass msg = f'
{tr("Параметры обфускатора сохранены и отправлены на роутеры", "Obfuscator params saved and pushed to routers")} ({pushed})
' + elif request.method == "POST" and request.form.get("action") == "add_preset": + ok, m = add_obf_preset(request.form.get("p_name", ""), request.form.get("p_ports", ""), + request.form.get("p_masking", "AUTO"), request.form.get("p_dummy", "4"), + request.form.get("p_idle", "300")) + msg = f'
{html_lib.escape(m)}
' + s = load_settings() + elif request.method == "POST" and request.form.get("action") == "del_preset": + n = del_obf_preset(request.form.get("preset_id", "")) + msg = f'
{tr("Пресет удалён, клиентов возвращено на основной", "Preset removed, clients moved back to default")}: {n}
' + s = load_settings() elif request.method == "POST" and request.form.get("action") == "save_srv_obf": masking = request.form.get("srv_masking", "AUTO").strip().upper() if masking not in ("AUTO", "STUN", "NONE"): @@ -3173,7 +4090,8 @@ def settings_page(): eps = s.get("endpoints", {}) ep_rows = "".join( f'
' - f'
' + f'' + f'
' for sv in get_all_servers_ordered()) _cu = f"{panel_base_url()}/server/phobos-cascade.sh" cascade_cmd = f"SELF_URL={_cu} bash <(curl -kfsSL {_cu}) enable {SERVER_IP} {_load_server_env().get('OBFUSCATOR_PORTS', '2083,5443,993')}" @@ -3247,7 +4165,7 @@ def settings_page(): stable — {tr("стабильная","stable")} · beta — {tr("тест","testing")} · dev — {tr("разработка","development")}. - {tr("С 1.4.0 все каналы открыты и берутся с git.bobyrev.com (GitHub — запасной). Токен больше не нужен.","Since 1.4.0 all channels are open and served from git.bobyrev.com (GitHub as fallback). No token needed.")}

+ {tr("stable — открытый, берётся с GitHub (adleibzon-rgb), запасной — git.bobyrev.com. beta и dev — для подписчиков Boosty: нужен токен подписчика (поле ниже).","stable is open, served from GitHub (adleibzon-rgb) with git.bobyrev.com as fallback. beta and dev are for Boosty subscribers and need a subscriber token (field below).")}

@@ -3312,6 +4230,22 @@ def settings_page():
+
+

{tr("Пресеты обфускатора","Obfuscator presets")} {hlp("Свой набор портов и ключ на группу клиентов: утёк пакет одного — остальные не затронуты. Пресет назначается клиенту в таблице «Клиенты». Работает на этом (основном) сервере; после смены пресета телефону нужно заново импортировать ссылку, роутер получит настройки сам.","Own ports and key for a group of clients: a leaked package does not expose the others. Assign a preset to a client in the Clients table. Applies to this (primary) server; after changing a preset a phone must re-import its link, routers update on their own.")}

+ + {"".join(f'' for p in s.get("obf_presets", [])) or f''} +
{tr("Имя","Name")}{tr("Порты (UDP)","Ports (UDP)")}maskingdummyidle{tr("Клиентов","Clients")}
{html_lib.escape(p["name"])}{p["ports"]}{p["masking"]}{p["max_dummy"]}{p["idle"]}{sum(1 for v in s.get("client_presets", {}).values() if v == p["id"])}
{tr("Пока только основной набор","Default set only")}
+
+ + + + + + + +
+
+

{tr("Информация о сервере","Server info")}

@@ -3530,8 +4464,10 @@ def secondary_api_request(server, path, data=None, timeout=10): def install_command(token, port=None): port = port or panel_port() - primary = f"{panel_scheme()}://{SERVER_IP}:{port}/init/{token}.sh" - urls = [primary, f"http://{SERVER_IP}/init/{token}.sh"] + host = client_endpoint(SERVER_IP) # domain if one is set for clients, else the IP + urls = [f"{panel_scheme()}://{host}:{port}/init/{token}.sh", f"http://{host}/init/{token}.sh"] + if host != SERVER_IP: + urls += [f"{panel_scheme()}://{SERVER_IP}:{port}/init/{token}.sh", f"http://{SERVER_IP}/init/{token}.sh"] return ("p=/tmp/phobos-init-$$.sh; ok=0; " f"for u in {' '.join(urls)}; do " "rm -f $p; " @@ -3723,6 +4659,101 @@ def download_apk(): return redirect(f"{PCA_GIT}/releases/download/{APK_TAG}/PhobosWG.apk") +MAC_PKG_URL = "https://git.bobyrev.com/PCA/PCA_Phobos-dev/releases/download/macos-1.4.3/PCA-Phobos-Mac-1.4.3.pkg" + + +def apple_apps(): + """iPhone / macOS apps are for beta and dev subscribers only; stable shows Android, Windows, Router, HR.""" + return _channel_of(panel_version()) != "stable" + + +WIN_ZIP_URL = "https://git.bobyrev.com/PCA/PCA_Phobos-dev/releases/download/windows-1.4.5-beta/PCA-Phobos-Windows-1.4.5-beta.zip" + + +@app.route("/download/windows") +@auth_required +def download_windows(): + """Windows client archive: private beta release on Gitea, fetched with the subscriber token saved in the panel.""" + import urllib.request + try: + tok = Path(GH_TOKEN_FILE).read_text().strip() + except Exception: + tok = "" + if not tok: + return (tr("Нужен токен подписчика: Настройки → Версия и обновления → «токен подписчика».", "Subscriber token needed: Settings → Version and updates → subscriber token."), 403) + try: + r = urllib.request.urlopen(urllib.request.Request(WIN_ZIP_URL, headers={"Authorization": "token " + tok}), timeout=30) + except Exception as e: + return (f"download failed: {e}", 502) + def gen(): + while True: + b = r.read(65536) + if not b: + break + yield b + return app.response_class(gen(), mimetype="application/zip", headers={ + "Content-Disposition": "attachment; filename=PCA-Phobos-Windows-1.4.5-beta.zip", + "Content-Length": r.headers.get("Content-Length", "")}) + + +@app.route("/download/macos") +@auth_required +def download_macos(): + """macOS installer (.pkg): private beta release on Gitea, fetched with the subscriber token saved in the panel.""" + import urllib.request + if not apple_apps(): + return ("macOS app is available in beta/dev", 404) + try: + tok = Path(GH_TOKEN_FILE).read_text().strip() + except Exception: + tok = "" + if not tok: + return (tr("Нужен токен подписчика: Настройки → Версия и обновления → «токен подписчика».", "Subscriber token needed: Settings → Version and updates → subscriber token."), 403) + try: + r = urllib.request.urlopen(urllib.request.Request(MAC_PKG_URL, headers={"Authorization": "token " + tok}), timeout=30) + except Exception as e: + return (f"download failed: {e}", 502) + def gen(): + while True: + b = r.read(65536) + if not b: + break + yield b + return app.response_class(gen(), mimetype="application/octet-stream", headers={ + "Content-Disposition": "attachment; filename=" + MAC_PKG_URL.rsplit("/", 1)[1], + "Content-Length": r.headers.get("Content-Length", "")}) + + +@app.route("/client//macos") +@auth_required +def client_macos(cid): + """macOS tab of the connection window (beta/dev): installer + phobos:// link.""" + if not apple_apps(): + return ("not found", 404) + conf, link = build_phone_config(cid, "android") + if conf is None or not link: + return ("client not found", 404) + steps = tr( + "1) Скачайте и откройте установщик (.pkg), macOS 12+ (Apple Silicon и Intel). Если macOS не даёт открыть — правый клик → «Открыть». 2) В строке меню появится значок «PCA Phobos» → «Импорт» → вставьте ссылку ниже (или файл .conf). 3) Значок → «Включить». Российские сайты идут напрямую, при блокировке UDP приложение само переходит на TCP. Команды в Терминале: phobos status, sudo phobos on|off.", + "1) Download and open the installer (.pkg), macOS 12+ (Apple Silicon and Intel). If macOS refuses, right-click → Open. 2) The “PCA Phobos” menu-bar icon → Import → paste the link below (or a .conf file). 3) Icon → Turn on. Russian sites go direct; if UDP is blocked the app switches to TCP by itself. Terminal: phobos status, sudo phobos on|off.") + copy, copied = tr("Копировать", "Copy"), tr("Скопировано", "Copied") + body = f"""

⬇ {tr("Скачать PCA Phobos для macOS (.pkg)", "Download PCA Phobos for macOS (.pkg)")}

+

{steps}

+
+ {html_lib.escape(link)} + +
+

⬇ {tr("Файл .conf", ".conf file")}

""" + return render(f""" +
+ {nav('clients')} +
+

{cid} — macOS

+ {body} +

{tr("← назад", "← back")}

+
""") + + @app.route("/client//conf/") @auth_required def client_conf_download(cid, mode): @@ -3747,7 +4778,10 @@ def client_phone(cid, mode): conf, link = build_phone_config(cid, mode) if conf is None: return ("client not found", 404) - qr = qr_datauri(link if (mode == "android" and link) else conf) + _alink = link + if mode == "ios": + _alink = build_phone_config(cid, "android")[1] + qr = qr_datauri(link if (mode == "android" and link) else (_alink or conf)) if mode == "android": title = tr("Android — PhobosWG (с обфускацией)", "Android — PhobosWG (obfuscated)") apk_btn = "" @@ -3775,11 +4809,28 @@ def client_phone(cid, mode): f'

{tr("phobos:// ссылка — импорт в PCA Phobos VPN или PhobosWG:", "phobos:// link — import into PCA Phobos VPN or PhobosWG:")} {tr("открыть", "open")}

' f'' f'

⬇ {tr("Файл .conf — для «Импорт из файла» в PCA Phobos VPN", ".conf file — for “Import from file” in PCA Phobos VPN")}. ' - f'{tr("Ссылка выше подходит и для HydraRoute (формат phobos:// автора Phobos).", "The link above also works in HydraRoute (upstream phobos:// format).")}

') + f'{tr("Для HydraRoute — отдельная вкладка HR.", "For HydraRoute use the HR tab.")}

') else: - title = tr("iPhone / iOS WireGuard (без обфускации)", "iPhone / iOS WireGuard (plain, no obfuscation)") - extra = (f'

📥 {tr("WireGuard в App Store","WireGuard on the App Store")} ⬇ {tr("Скачать .conf","Download .conf")}

' - f'

{tr("⚠ Обычный WireGuard без маскировки (Endpoint → :51820). На сервере должен быть открыт порт 51820 (ALLOW_PLAIN_WG=1).", "⚠ Plain WireGuard, no obfuscation (Endpoint → :51820). Port 51820 must be open on the server (ALLOW_PLAIN_WG=1).")}

') + title = tr("iPhone — PCA Phobos VPN (с обфускацией)", "iPhone — PCA Phobos VPN (obfuscated)") + tf = (load_settings().get("testflight_url") or "https://testflight.apple.com/join/tHqjPg1p").strip() + extra = ( + f'
' + f'{tr("Если приложение уже стоит:", "If the app is already installed:")}
    ' + f'
  1. {tr("Наведи камеру iPhone на QR выше и нажми на ссылку — откроется PCA Phobos VPN. Или нажми кнопку ниже прямо на телефоне.", "Point the iPhone camera at the QR above and tap the link, or tap the button below on the phone.")}
  2. ' + f'
  3. {tr("Приложение предложит импортировать профиль — подтверди.", "The app offers to import the profile — confirm.")}
  4. ' + f'
  5. {tr("Нажми «Подключить» и разреши добавление VPN (один раз).", "Tap Connect and allow adding the VPN (once).")}
  6. ' + f'
  7. {tr("Сервер выбирается сам: при блокировке приложение переключится на другой сервер без твоего участия.", "The server is picked automatically; if it is blocked the app switches to another one by itself.")}
' + f'{tr("Если приложения ещё нет:", "If the app is not installed yet:")} {tr("поставь TestFlight из App Store и открой", "install TestFlight from the App Store and open")} {tf}, {tr("потом действуй по шагам выше. Ссылку на приложение никому не передавай.", "then follow the steps above. Do not share the app link.")}
' + f'

📱 {tr("Открыть в PCA Phobos VPN", "Open in PCA Phobos VPN")} TestFlight

' + f'' + f'
{tr("Обычный WireGuard без обфускации (запасной вариант)", "Plain WireGuard, no obfuscation (fallback)")}' + f'

📥 WireGuard ⬇ {tr("Скачать .conf","Download .conf")}

' + f'

{tr("⚠ Endpoint → :51820, на сервере нужен открытый порт 51820 (ALLOW_PLAIN_WG=1).", "⚠ Endpoint → :51820, port 51820 must be open on the server (ALLOW_PLAIN_WG=1).")}

') + if mode == "ios" and not apple_apps(): + title = tr("iPhone — обычный WireGuard (без обфускации)", "iPhone — plain WireGuard (no obfuscation)") + qr = qr_datauri(conf) + extra = (f'

📥 WireGuard ⬇ {tr("Скачать .conf","Download .conf")}

' + f'

{tr("⚠ Endpoint → :51820, на сервере нужен открытый порт 51820 (ALLOW_PLAIN_WG=1). Приложение PCA Phobos VPN для iPhone (с обфускацией) — в каналах beta/dev.", "⚠ Endpoint → :51820, port 51820 must be open (ALLOW_PLAIN_WG=1). The obfuscated PCA Phobos VPN iPhone app is in beta/dev.")}

') html = f"""
{nav('clients')} @@ -3809,6 +4860,71 @@ def router_config(client_id): return (conf, 200, {"Content-Type": "text/plain; charset=utf-8"}) +@app.route("/client//windows") +@auth_required +def client_windows(cid): + """Windows tab of the connection window: phobos:// link (with pca-sub) for PCA Phobos for Windows.""" + conf, link = build_phone_config(cid, "android") + if conf is None or not link: + return ("client not found", 404) + steps = tr( + "1) Установите «PCA Phobos для Windows» (архив из релиза beta, install.cmd от администратора). 2) Вставьте ссылку ниже: окно запроса ключа или команда PCAPhobos.exe import ССЫЛКА. 3) Значок в трее → включить. Смена сервера в панели подхватывается приложением само (раз в минуту); другие клиенты — только повторным импортом ссылки.", + "1) Install “PCA Phobos for Windows” (archive from the beta release, run install.cmd as administrator). 2) Paste the link below when asked for the key, or run PCAPhobos.exe import LINK. 3) Tray icon → turn on. The app follows server changes from the panel by itself (every minute); other clients need the link re-imported.") + copy, copied = tr("Копировать", "Copy"), tr("Скопировано", "Copied") + body = f"""

⬇ {tr("Скачать PCA Phobos для Windows (zip)", "Download PCA Phobos for Windows (zip)")} {tr("в архиве: PCAPhobos — весь трафик кроме РФ, PCAPhobosTG — только Telegram", "archive: PCAPhobos — all but RU, PCAPhobosTG — Telegram only")}

+

{steps}

+
+ {html_lib.escape(link)} + +
""" + html = f""" +
+ {nav('clients')} +
+

{cid} — Windows

+ {body} +

{tr("← назад", "← back")}

+
""" + return render(html) + + +def build_hr_link(cid): + """phobos:// link strictly per the upstream spec (docs/phobos-link-format.md) for HydraRoute: + no pca-* comment lines (HR drops them anyway, and pca-sub carries a panel token), every mandatory field padded with "= none".""" + import base64, urllib.parse + conf, _link = build_phone_config(cid, "android") + if conf is None: + return None + conf = _pad_conf_with_none("\n".join(ln for ln in conf.split("\n") if not ln.lstrip().startswith("#"))) + return "phobos://" + base64.urlsafe_b64encode(conf.encode()).decode().rstrip("=") + "#" + urllib.parse.quote(cid or "none") + + +@app.route("/client//hr") +@auth_required +def client_hr(cid): + """HR tab of the connection window: link for HydraRoute Neo on a Keenetic.""" + link = build_hr_link(cid) + if not link: + return ("client not found", 404) + steps = tr( + "HydraRoute Neo на роутере: «Сеть» → «+ Интерфейс» → тип OpkgTun → вставьте ссылку в поле «Ссылки» → «Создать интерфейс». Через HydraRoute идёт один сервер по UDP: резервного TCP-канала и автоперехода между серверами нет. Один клиент — одно устройство: не используйте эту же учётку на телефоне.", + "HydraRoute Neo on the router: Network → + Interface → type OpkgTun → paste the link into Links → Create interface. HydraRoute uses one server over UDP: no TCP fallback and no failover between servers. One client — one device: do not use the same client on a phone.") + copy, copied = tr("Копировать", "Copy"), tr("Скопировано", "Copied") + html = f""" +
+ {nav('clients')} +
+

{cid} — HydraRoute

+

{steps}

+
+ {html_lib.escape(link)} + +
+

{tr("← назад", "← back")}

+
""" + return render(html) + + @app.route("/client//router-config") @auth_required def client_router_config(cid): @@ -4410,7 +5526,7 @@ def api_client_remove(): except Exception: pass s = load_settings() - for key in ("router_access", "client_assignments"): + for key in ("router_access", "client_assignments", "client_pinned"): d = s.get(key, {}) if cid in d: del d[cid] @@ -4477,6 +5593,23 @@ def servers_page(): synced = sync_missing_peers_to_secondaries(servers[-1]) msg = f'
Сервер {ip} добавлен, синхронизировано {synced} клиентов
' + elif action == "set_domain": + ip = request.form.get("ip", "").strip() + host = request.form.get("domain", "").strip().lower() + form = {"ep_" + sv["ip"]: client_endpoint(sv["ip"]) for sv in get_all_servers_ordered()} + form = {k: ("" if v == k[3:] else v) for k, v in form.items()} + form["ep_" + ip] = host + ok, m = save_client_endpoints(form) + warn = "" + if ok and host: + import socket + try: + if ip not in socket.gethostbyname_ex(host)[2]: + warn = " " + tr("⚠ Домен сейчас указывает не на этот сервер — проверь A-запись.", "⚠ The domain does not point to this server right now — check the A record.") + except OSError: + warn = " " + tr("⚠ Домен не резолвится — создай A-запись на IP сервера.", "⚠ The domain does not resolve — create an A record to the server IP.") + msg = f'
{html_lib.escape(m)}{html_lib.escape(warn)}
' + elif action == "remove": ip = request.form.get("ip", "").strip() servers = [sv for sv in servers if sv.get("ip") != ip] @@ -4660,10 +5793,14 @@ def servers_page(): usable = [ip for ip in server_ips if base_load[ip] < 0.99] or server_ips placed = {ip: 0 for ip in server_ips} PEN = 0.08 # extra load each assigned client adds to a server's score - assignments = {} + pinned = s.get("client_pinned", {}) + old_as = s.get("client_assignments", {}) + assignments = {cid: ip for cid, ip in old_as.items() if pinned.get(cid)} rb_err = [] for c in clients: cid = c["client_id"] + if cid in assignments: + continue # закреплён за сервером — не двигаем target = min(usable, key=lambda ip: base_load[ip] + placed[ip] * PEN) placed[target] += 1 assignments[cid] = target @@ -4783,6 +5920,8 @@ def servers_page(): rows += f"""
+ """ @@ -4860,7 +5999,7 @@ def servers_page():

{tr("Приоритет серверов и балансировка", "Server Priority & Load Balancing")} {hlp("У каждого клиента есть основной сервер. При сбое роутер сам перебирает серверы сверху вниз. Стрелки меняют общий порядок резервирования. Роутеры за NAT подтягивают конфиг сами — ручных действий не нужно.", "Each client has a primary server. On failure the router tries servers top-to-bottom by itself. Arrows change the global fallback order. NAT routers pull config themselves — no manual action needed.")}

{prio}{ip}{ptag}{ports}{peers_val}{assigned} {st_text}
+
{mv} {term_btn} {acts}
- + {rows}
{tr("Сервер","Server")}{tr("Порты","Ports")}Peers{tr("Назначено","Assigned")}{tr("Статус","Status")}{tr("Действия","Actions")}
{tr("Сервер","Server")}{tr("Порты","Ports")}Peers{tr("Назначено","Assigned")}{tr("Статус","Status")}{tr("Домен для клиентов","Client domain")} {hlp("Если у сервера есть домен, впиши его — клиенты, ссылки для телефонов, роутеры и команда установки будут использовать его вместо IP. Нужна A-запись домена на IP сервера. Пусто — используется IP.","If the server has a domain, enter it - clients, phone links, routers and the install command will use it instead of the IP. Needs an A record pointing to the server IP. Empty = the IP is used.")}{tr("Действия","Actions")}
diff --git a/client/phobos-health.sh b/client/phobos-health.sh index 00a9c44..d4111c9 100644 --- a/client/phobos-health.sh +++ b/client/phobos-health.sh @@ -6,11 +6,19 @@ # ============================================================ PHOBOS_DIR="/opt/etc/Phobos" -CONF="$PHOBOS_DIR/failover.conf" -STATE="$PHOBOS_DIR/state" -LOG="$PHOBOS_DIR/health.log" +# 02.11.2026: несколько учёток на одном роутере. Файлы учётки (failover.conf, state, +# client_id, panel_*) — в PHOBOS_ACC (доп. учётки: $PHOBOS_DIR/acc/<клиент>), +# главная — прямо в $PHOBOS_DIR. Конфиги обфускаторов общие, свой ищем по *.link. +ACC_DIR="${PHOBOS_ACC:-$PHOBOS_DIR}" +CONF="$ACC_DIR/failover.conf" +STATE="$ACC_DIR/state" +LOG="$ACC_DIR/health.log" +CID=$(cat "$ACC_DIR/client_id" 2>/dev/null) OBF_CONF="$PHOBOS_DIR/wg-obfuscator.conf" -LOCKFILE="/tmp/phobos-health.lock" +for l in "$PHOBOS_DIR"/wg-obfuscator*.link; do + [ -n "$CID" ] && [ "$(cat "$l" 2>/dev/null)" = "$CID" ] && OBF_CONF="${l%.link}.conf" && break +done +LOCKFILE="/tmp/phobos-health${PHOBOS_ACC:+-$CID}.lock" WG_IF="Wireguard3" MAX_LOG_LINES=200 @@ -25,9 +33,9 @@ PRIMARY_CHECK_INTERVAL=300 # wg-obfuscator-* services are active — the bare panel port stays up even # when the tunnel path is dead, so we must NOT switch back on panel liveness # alone (that caused premature switchback to a dead primary). -PRIMARY_PROBE_SCHEME="${PRIMARY_PROBE_SCHEME:-$(cat "$PHOBOS_DIR/panel_scheme" 2>/dev/null)}" +PRIMARY_PROBE_SCHEME="${PRIMARY_PROBE_SCHEME:-$(cat "$ACC_DIR/panel_scheme" 2>/dev/null)}" PRIMARY_PROBE_SCHEME="${PRIMARY_PROBE_SCHEME:-http}" -PRIMARY_PROBE_PORT="${PRIMARY_PROBE_PORT:-$(cat "$PHOBOS_DIR/panel_port" 2>/dev/null)}" +PRIMARY_PROBE_PORT="${PRIMARY_PROBE_PORT:-$(cat "$ACC_DIR/panel_port" 2>/dev/null)}" PRIMARY_PROBE_PORT="${PRIMARY_PROBE_PORT:-8443}" PRIMARY_PROBE_PATH="/api/obf-health" # Connectivity check targets (logged for context only) @@ -55,6 +63,16 @@ mkdir -p "$STATE" # ── Detect WG interface ── detect_wg_interface() { + # точное совпадение "Phobos-<клиент>"; доп. учётка не должна схватить чужой интерфейс + for i in 0 1 2 3 4 5 6 7 8 9; do + desc=$(ndmc -c "show interface Wireguard${i}" 2>/dev/null | grep description | head -1) + # HydraRoute Neo names the interface by the bare client id (09.33) + if [ -n "$CID" ] && echo "$desc" | grep -qE "(Phobos-|: )${CID}\$"; then + WG_IF="Wireguard${i}" + return 0 + fi + done + [ -n "$PHOBOS_ACC" ] && return 1 for i in 0 1 2 3 4 5 6 7 8 9; do desc=$(ndmc -c "show interface Wireguard${i}" 2>/dev/null | grep description | head -1) if echo "$desc" | grep -qi phobos; then @@ -65,6 +83,19 @@ detect_wg_interface() { return 1 } +# ── Restart own obfuscator (init script whose ARGS name our config) ── +restart_obf() { + sc=$(grep -l "/$(basename "$OBF_CONF")\"\$" /opt/etc/init.d/S*obfuscator* 2>/dev/null | head -1) + [ -z "$sc" ] && [ -f /opt/etc/init.d/S49wg-obfuscator ] && sc=/opt/etc/init.d/S49wg-obfuscator + if [ -n "$sc" ]; then + "$sc" restart >/dev/null 2>&1 + else + killall wg-obfuscator 2>/dev/null + sleep 1 + wg-obfuscator --config "$OBF_CONF" & + fi +} + # ── Read failover config ── if [ ! -f "$CONF" ]; then log "ERROR: no failover.conf" @@ -80,6 +111,7 @@ while true; do eval "SERVER_${idx}_PORTS=$(echo "$val" | cut -d: -f2-)" eval "SERVER_${idx}_KEY=$(grep "^KEY_${idx}=" "$CONF" 2>/dev/null | cut -d= -f2-)" eval "SERVER_${idx}_WGKEY=$(grep "^WGKEY_${idx}=" "$CONF" 2>/dev/null | cut -d= -f2-)" + eval "SERVER_${idx}_TCP=$(grep "^TCP_${idx}=" "$CONF" 2>/dev/null | cut -d= -f2-)" SERVER_COUNT=$idx idx=$((idx + 1)) done @@ -94,14 +126,26 @@ CURRENT_SERVER=$(cat "$STATE/current_server" 2>/dev/null || echo "1") CURRENT_PORT_IDX=$(cat "$STATE/current_port_idx" 2>/dev/null || echo "0") FAIL_COUNT=$(cat "$STATE/fail_count" 2>/dev/null || echo "0") PRIMARY_CHECK_TS=$(cat "$STATE/primary_check_ts" 2>/dev/null || echo "0") +TCP_MODE=$(cat "$STATE/tcp_mode" 2>/dev/null || echo "0") # 0 = UDP, N = TCP via server N +CYCLES=$(cat "$STATE/udp_cycles" 2>/dev/null || echo "0") # full UDP rounds without success +UDPTCP_BIN=/opt/bin/udptcp +UDPTCP_LPORT=13256 -# ── Detect Phobos WG interface (non-fatal, fallback to Wireguard3) ── -# Нет своего интерфейса — выходим: запасной Wireguard3 бывает чужим туннелем. +# ── Detect Phobos WG interface ── +# Нет своего интерфейса — выходим: запасной Wireguard3 бывает чужим (так сторож +# удалённой доп. учётки вписал пир Phobos в посторонний туннель). detect_wg_interface || { log "ERROR: own WG interface not found, skip" exit 0 } +# ── Panel tunnel IP must go through our tunnel (HydraRoute-made interface has no +# such route → pull went to WAN and hung, 09.33) ── +PT_IP=$(cat "$ACC_DIR/panel_tunnel_ip" 2>/dev/null) +if [ -n "$PT_IP" ] && ip route get "$PT_IP" 2>/dev/null | grep -q "dev ppp\|dev eth\|dev wan"; then + ndmc -c "ip route $PT_IP 255.255.255.255 $WG_IF auto" >/dev/null 2>&1 && log "ROUTE: $PT_IP via $WG_IF" +fi + # ── Get handshake age via ndmc ── # A peer switch can leave the removed peer's stale handshake listed in ndmc # output (often a huge sentinel like 2147483647). Take the FRESHEST (minimum @@ -151,9 +195,13 @@ list_iface_keys() { count_server_peers() { present=$(list_iface_keys) n=0 + seen=" " i=1 while [ "$i" -le "$SERVER_COUNT" ]; do eval "wk=\$SERVER_${i}_WGKEY" + # one server may have several addresses = same key in several entries + case "$seen" in *" $wk "*) i=$((i + 1)); continue ;; esac + seen="$seen$wk " if [ -n "$wk" ] && echo "$present" | grep -q "^${wk}$"; then n=$((n + 1)) fi @@ -194,7 +242,7 @@ switch_wg_peer() { # 01.11.2026: порт — из конфига обфускатора (было зашито 13255, а установщик # берёт свободный, на части роутеров 51820 → после переключения туннель мёртв). # Через ndmc по шагам: JSON-RCI с allow-ips "::"/0 молча не ставил allow-ips. - lport=$(grep 'source-lport' "$PHOBOS_DIR/wg-obfuscator.conf" 2>/dev/null | tr -d ' ' | cut -d'=' -f2) + lport=$(grep 'source-lport' "$OBF_CONF" 2>/dev/null | tr -d ' ' | cut -d'=' -f2) lport=${lport:-13255} ndmc -c "interface $WG_IF wireguard peer $new_key" >/dev/null 2>&1 ndmc -c "interface $WG_IF wireguard peer $new_key endpoint 127.0.0.1:$lport" >/dev/null 2>&1 @@ -226,6 +274,7 @@ switch_endpoint() { fi log "SWITCH → server $server_idx ($host:$port)" + stop_tcp # 1. Switch WG peer key if different server switch_wg_peer "$wg_key" @@ -239,13 +288,7 @@ switch_endpoint() { fi # 3. Restart obfuscator - if [ -f /opt/etc/init.d/S49wg-obfuscator ]; then - /opt/etc/init.d/S49wg-obfuscator restart >/dev/null 2>&1 - else - killall wg-obfuscator 2>/dev/null - sleep 1 - wg-obfuscator --config "$OBF_CONF" & - fi + restart_obf # 4. Save state (fail_count is managed by the caller, NOT reset here — # resetting on a port-hop would prevent escalation to server failover) @@ -253,6 +296,45 @@ switch_endpoint() { echo "$port_idx" > "$STATE/current_port_idx" } +# ── TCP channel (09.33/1.5.0): obfuscated UDP inside TCP via local udptcp ── +stop_tcp() { + [ "$TCP_MODE" = "0" ] && ! pidof udptcp >/dev/null 2>&1 && return 0 + killall udptcp 2>/dev/null + TCP_MODE=0 + echo 0 > "$STATE/tcp_mode" +} + +# start_tcp N — tunnel through server N's TCP port; 1 = started +start_tcp() { + n=$1 + eval "host=\$SERVER_${n}_HOST" + eval "tport=\$SERVER_${n}_TCP" + eval "obf_key=\$SERVER_${n}_KEY" + eval "wg_key=\$SERVER_${n}_WGKEY" + [ -n "$tport" ] && [ -x "$UDPTCP_BIN" ] || return 1 + killall udptcp 2>/dev/null + "$UDPTCP_BIN" -c "127.0.0.1:${UDPTCP_LPORT}" "${host}:${tport}" >/dev/null 2>&1 & + switch_wg_peer "$wg_key" + sed -i "s|^target = .*|target = 127.0.0.1:${UDPTCP_LPORT}|" "$OBF_CONF" + [ -n "$obf_key" ] && sed -i "s|^key = .*|key = ${obf_key}|" "$OBF_CONF" + restart_obf + TCP_MODE=$n + echo "$n" > "$STATE/tcp_mode" + echo "$n" > "$STATE/current_server" + log "TCP MODE → server $n (${host}:${tport} over TCP)" + return 0 +} + +# try_tcp_after N — first server after N (no wrap) that has a TCP port; 1 = none +try_tcp_after() { + i=$(( $1 + 1 )) + while [ "$i" -le "$SERVER_COUNT" ]; do + start_tcp "$i" && return 0 + i=$((i + 1)) + done + return 1 +} + # ── Try next port on current server ── try_next_port() { port_count=$(get_port_count "$CURRENT_SERVER") @@ -267,7 +349,9 @@ try_next_port() { # ── Try next server ── try_next_server() { next=$((CURRENT_SERVER + 1)) - [ "$next" -gt "$SERVER_COUNT" ] && next=1 + if [ "$next" -gt "$SERVER_COUNT" ]; then + next=1 + fi [ "$next" -eq "$CURRENT_SERVER" ] && return 1 log "FAILOVER → server $next" @@ -277,7 +361,7 @@ try_next_server() { # ── Check if primary is back ── check_primary() { - [ "$CURRENT_SERVER" -eq 1 ] && return + [ "$CURRENT_SERVER" -eq 1 ] && [ "$TCP_MODE" = "0" ] && return now=$(date +%s) elapsed=$((now - PRIMARY_CHECK_TS)) @@ -299,7 +383,10 @@ check_primary() { fi done if [ -n "$ok_url" ]; then - log "PRIMARY ($ok_url http=200) alive, switching back" + # HTTP alive does not mean UDP passes (провайдер режет UDP к IP, 09.33): + # пробуем, а если рукопожатия не будет — trial_check вернёт как было. + log "PRIMARY ($ok_url http=200) alive, TRIAL switch back" + echo "$CURRENT_SERVER $CURRENT_PORT_IDX $TCP_MODE $(date +%s)" > "$STATE/trial" switch_endpoint 1 0 echo "0" > "$STATE/fail_count" else @@ -341,6 +428,8 @@ heal_lan_routing() { while read -r mac pol; do [ -z "$mac" ] && continue case "$mac" in \#*) continue;; esac + # политику удалили — не воскрешаем привязку к ней + echo "$rc" | grep -qx "ip policy $pol" || continue if ! echo "$rc" | grep -q "host $mac policy $pol"; then ndmc -c "ip hotspot host $mac policy $pol" >/dev/null 2>&1 log "HEAL: re-bound host $mac -> $pol" @@ -417,12 +506,31 @@ fi AGE=$(get_handshake_age) WAN=$(check_connectivity && echo "yes" || echo "no") +# ── Trial of the primary: fresh handshake within ~2 min or go back ── +if [ -f "$STATE/trial" ]; then + read t_srv t_port t_tcp t_ts < "$STATE/trial" + if [ "$AGE" -lt "$HANDSHAKE_WARN" ]; then + log "TRIAL ok: primary works" + rm -f "$STATE/trial"; echo 0 > "$STATE/udp_cycles" + elif [ $(( $(date +%s) - ${t_ts:-0} )) -ge 110 ]; then + rm -f "$STATE/trial" + log "TRIAL failed: back to server $t_srv" + if [ "${t_tcp:-0}" != "0" ]; then start_tcp "$t_tcp" || switch_endpoint "$t_srv" "$t_port" + else switch_endpoint "$t_srv" "$t_port"; fi + echo 0 > "$STATE/fail_count" + exit 0 + else + exit 0 # give the trial time + fi +fi + # Tunnel healthy = fresh handshake if [ "$AGE" -lt "$HANDSHAKE_WARN" ]; then if [ "$FAIL_COUNT" -gt 0 ]; then log "OK: tunnel up (handshake=${AGE}s, server=$CURRENT_SERVER, wan=$WAN)" echo "0" > "$STATE/fail_count" fi + echo 0 > "$STATE/udp_cycles" check_primary exit 0 fi @@ -438,23 +546,30 @@ log "STALE: handshake=${AGE}s wan=${WAN} server=$CURRENT_SERVER port=$CURRENT_PO # Stage 1 (fail 1): Restart obfuscator if [ "$FAIL_COUNT" -eq 1 ]; then log "ACTION: restart obfuscator" - if [ -f /opt/etc/init.d/S49wg-obfuscator ]; then - /opt/etc/init.d/S49wg-obfuscator restart >/dev/null 2>&1 - else - killall wg-obfuscator 2>/dev/null - sleep 1 - wg-obfuscator --config "$OBF_CONF" & - fi + restart_obf exit 0 fi -# Stage 2 (fail 2): Port hop to an alternate port on the SAME server -if [ "$FAIL_COUNT" -eq 2 ]; then +# TCP mode went stale → back to the UDP round (next server) +if [ "$TCP_MODE" != "0" ] && [ "$FAIL_COUNT" -ge 2 ]; then + log "TCP stale on server $TCP_MODE" + if try_tcp_after "$TCP_MODE"; then echo 0 > "$STATE/fail_count"; exit 0; fi + stop_tcp + switch_endpoint 1 0; echo 0 > "$STATE/fail_count"; exit 0 +fi + +# Stage 2 (fail 2+): hop through ALL ports of the SAME server (was: only one) +if [ "$FAIL_COUNT" -ge 2 ]; then if try_next_port; then exit 0 fi - # only one port → fall through to server failover - log "single port, escalate to failover" + log "ports of server $CURRENT_SERVER exhausted, escalate to failover" +fi + +# Whole UDP round failed (all servers × ports) → TCP, if any server offers it +if [ "$CURRENT_SERVER" -eq "$SERVER_COUNT" ]; then + CYCLES=$((CYCLES + 1)); echo "$CYCLES" > "$STATE/udp_cycles" + if try_tcp_after 0; then echo 0 > "$STATE/fail_count"; exit 0; fi fi # Stage 3 (fail 3+): Server failover. Reset fail_count so the new server diff --git a/docs/PORTS.md b/docs/PORTS.md index b471d37..eab75e0 100644 --- a/docs/PORTS.md +++ b/docs/PORTS.md @@ -11,6 +11,8 @@ fails", or "panel is too exposed". | `PANEL_PORT` | TCP | admin browser, secondary setup/register, router pull fallback | yes | Web panel and `/api/*`. Real value: `cat /opt/phobos-panel/.port`. | | `80` | TCP | routers during install, Android APK download | recommended | nginx serves `/init/`, `/packages/`, `/app/`. | | `2083,5443,993` | UDP | routers and Android PhobosWG | yes | wg-obfuscator public entry ports. Real value: `grep OBFUSCATOR_PORTS /opt/Phobos/server/server.env`. | +| `443` (else `8443`/`2096`/`4433`) | TCP | PCA Phobos VPN apps, Windows, routers (1.5.0+) when UDP is blocked | recommended | TCP fallback `phobos-tcp` (udptcp) since 1.4.1; port chosen at install/update, shown in `phobos-update` output (`TCP fallback: port N`). | +| second public IP: obfuscator ports + TCP | UDP/TCP | routers whose ISP blocks the main IP | optional | `phobos-altip` (1.5.0) DNATs the second IP to the main one; list it in panel → «Адрес для клиентов» → spare addresses. | | `51820` | UDP | plain WireGuard clients only | no by default | Usually blocked to force obfuscation. Open only if installed with `ALLOW_PLAIN_WG=1` for iOS/plain WG. | | `22` | TCP | admin only | optional | SSH maintenance. Do not expose broadly. | diff --git a/docs/SERVER-SWITCH.md b/docs/SERVER-SWITCH.md new file mode 100644 index 0000000..c884f86 --- /dev/null +++ b/docs/SERVER-SWITCH.md @@ -0,0 +1,47 @@ +# Смена сервера у клиента: что и как работает + +Клиент (учётка) в панели привязан к серверу. Если в панели открыть «Редактировать» и выбрать другой +сервер, адрес меняется у клиента **автоматически только в наших приложениях**. Сторонние клиенты +(PhobosWG и др.) ничего не знают о панели — им нужно заново импортировать ссылку `phobos://`. + +## Кто подхватывает смену сервера + +| Клиент | Как узнаёт о смене | Задержка | +|---|---|---| +| Роутер (Keenetic/OpenWrt) | pull-агент `phobos-pull.sh` раз в минуту забирает `failover.conf`; `phobos-health.sh` видит рассинхрон (DESYNC) и переключает (SWITCH) | до ~1 мин | +| iOS (build 7+), Android 1.4.5+ | подписка `# pca-sub` в ссылке: приложение читает `/api/phone-sub/<клиент>?token=…` | при запуске и по таймеру | +| Windows 1.4.5+ | помощник `sub` читает `# pca-sub`, раз в минуту; при другом host/ключе переписывает `profile.conf` и переподключается | до ~1 мин | +| Сторонние (PhobosWG и др.) | никак | переимпорт ссылки вручную | + +## Как это устроено +1. Панель → Клиенты → «Редактировать» → «Сервер» (поле `client_assignments` в `settings.json`). +2. Панель добавляет WG-пир клиента на выбранный сервер и рассылает конфиг (`fanout_router_config`). +3. «Переход на другие серверы»: «Да, при блокировке» — выбранный сервер первый, остальные запасные; + «Только этот сервер» (`client_pinned`) — в списке один выбранный сервер. +4. Список отдаётся роутеру (`failover.conf`) и телефонам/Windows (`/api/phone-sub`); первый в списке — назначенный. + +## Что нужно, чтобы это работало +- **Проверено 03.11.2026** (главная панель на ВМ + secondary на VPS): регистрация (`registered`), пир клиента появляется на secondary, «Сервер» меняет порядок в `/api/phone-sub` (назначенный первый), «Только этот сервер» оставляет один. Команда secondary в панели сама подставляет подсеть главной. +- **Один сервер управления.** Остальные серверы ставятся как secondary командой из панели + (раздел «Серверы»): общий список клиентов и адресов. Отдельная панель на втором VPS (полный + `install.sh`) — это другая панель со своими клиентами, смена сервера между ними не работает. +- Если всё же ставите независимые серверы — у каждого своя подсеть (`10.25.0.0/16`, `10.25.11.0/24`, …), + иначе клиенты получат одинаковые IP и второй туннель на роутере не поднимется. + Подсеть вида `10.25.11.0/16` установщик превращает в `10.25.11.0/24`. +- В ссылке клиента должна быть строка `# pca-sub = …`. Старые ссылки без неё импортировать заново один раз. +- Несколько учёток на одном роутере (по стране на учётку): у каждой «Только этот сервер», в beta/dev + они ставятся в `/opt/etc/Phobos/acc/<учётка>/`. Не пробовать на канале stable (v1.4.7): там затрёт главную. + +## Как проверить за минуту +```bash +# что панель отдаёт клиенту (первый сервер = назначенный) +curl -s "http://ПАНЕЛЬ:ПОРТ/api/phone-sub/КЛИЕНТ?token=ТОКЕН" +# роутер: новый сервер и журнал переключения +cat /opt/etc/Phobos/failover.conf | grep ^SERVER_1; tail /opt/etc/Phobos/health.log +# Windows: журнал и профиль +type C:\ProgramData\PCAPhobos\pcaphobos.log # «panel moved us to … -> reconnect» +``` + +## Ограничения +- Windows-помощник сравнивает host, ключ обфускатора и WG-ключ; смена только порта не вызывает переподключения. +- Пока клиент оффлайн, он узнает о смене при следующем запуске/подключении. diff --git a/docs/img/clients-dark.png b/docs/img/clients-dark.png index 3f822c9..645fdf3 100644 Binary files a/docs/img/clients-dark.png and b/docs/img/clients-dark.png differ diff --git a/docs/img/clients.png b/docs/img/clients.png index 9d8e27d..91affe9 100644 Binary files a/docs/img/clients.png and b/docs/img/clients.png differ diff --git a/docs/img/ios-app.png b/docs/img/ios-app.png deleted file mode 100644 index ce53f68..0000000 Binary files a/docs/img/ios-app.png and /dev/null differ diff --git a/docs/img/mobile-clients-dark.png b/docs/img/mobile-clients-dark.png index 77627b3..08e1216 100644 Binary files a/docs/img/mobile-clients-dark.png and b/docs/img/mobile-clients-dark.png differ diff --git a/docs/img/mobile-clients.png b/docs/img/mobile-clients.png index 3120c7f..680b611 100644 Binary files a/docs/img/mobile-clients.png and b/docs/img/mobile-clients.png differ diff --git a/docs/img/mobile-servers-dark.png b/docs/img/mobile-servers-dark.png new file mode 100644 index 0000000..c9082b4 Binary files /dev/null and b/docs/img/mobile-servers-dark.png differ diff --git a/docs/img/mobile-servers.png b/docs/img/mobile-servers.png index 89df012..9e89f09 100644 Binary files a/docs/img/mobile-servers.png and b/docs/img/mobile-servers.png differ diff --git a/docs/img/servers-dark.png b/docs/img/servers-dark.png index 88c9d14..955d4fa 100644 Binary files a/docs/img/servers-dark.png and b/docs/img/servers-dark.png differ diff --git a/docs/img/servers.png b/docs/img/servers.png index 8842aff..429b4d6 100644 Binary files a/docs/img/servers.png and b/docs/img/servers.png differ diff --git a/docs/img/sessions-dark.png b/docs/img/sessions-dark.png index 05ac22b..d2713a1 100644 Binary files a/docs/img/sessions-dark.png and b/docs/img/sessions-dark.png differ diff --git a/docs/img/sessions.png b/docs/img/sessions.png index 3c77619..0e2d62b 100644 Binary files a/docs/img/sessions.png and b/docs/img/sessions.png differ diff --git a/docs/img/settings-dark.png b/docs/img/settings-dark.png index a3253ca..9865800 100644 Binary files a/docs/img/settings-dark.png and b/docs/img/settings-dark.png differ diff --git a/docs/img/settings.png b/docs/img/settings.png index b2048a8..9bad7a4 100644 Binary files a/docs/img/settings.png and b/docs/img/settings.png differ diff --git a/install.sh b/install.sh index 52eaf0e..aaa7f3c 100755 --- a/install.sh +++ b/install.sh @@ -19,6 +19,7 @@ # OBF_PORTS 2083,5443,993 obfuscator listen ports # TG_TOKEN / TG_CHAT Telegram alerts # PCA_BRANCH main branch to pull PCA files from +# SKIP_NGINX 0 1 = do not touch an existing nginx # ============================================================ set -e @@ -104,7 +105,8 @@ print(ipaddress.ip_network(sys.argv[1], strict=False).prefixlen) PY )" -SERVER_IP=$(curl -s -m8 https://api.ipify.org || hostname -I | awk '{print $1}') +# ponytail: SERVER_IP можно задать явно — для серверов с несколькими IP +SERVER_IP="${SERVER_IP:-$(curl -s -m8 https://api.ipify.org || hostname -I | awk '{print $1}')}" IFACE=$(ip route get 8.8.8.8 2>/dev/null | awk '{for(i=1;i/dev/null || true cat > /etc/nginx/sites-available/phobos <<'NGINX' server { @@ -352,8 +356,9 @@ server { } NGINX ln -sf /etc/nginx/sites-available/phobos /etc/nginx/sites-enabled/phobos -mkdir -p /opt/Phobos/www/app; chmod 755 /opt/Phobos/www /opt/Phobos/www/init /opt/Phobos/www/packages /opt/Phobos/www/app nginx -t >/dev/null 2>&1 && systemctl enable nginx -q 2>/dev/null && systemctl restart nginx || echo " WARN: nginx config test failed" +fi +mkdir -p /opt/Phobos/www/app; chmod 755 /opt/Phobos/www /opt/Phobos/www/init /opt/Phobos/www/packages /opt/Phobos/www/app # ── 9. router watchdog (auto reboot-recovery) ── echo "[9/9] router watchdog..." diff --git a/ios/.gitignore b/ios/.gitignore deleted file mode 100644 index ff2c642..0000000 --- a/ios/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -*.xcuserstate -xcuserdata/ diff --git a/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index e13552c..0000000 --- a/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1 +0,0 @@ -{"images":[{"filename":"icon-1024.png","idiom":"universal","platform":"ios","size":"1024x1024"}],"info":{"author":"xcode","version":1}} diff --git a/ios/App/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/ios/App/Assets.xcassets/AppIcon.appiconset/icon-1024.png deleted file mode 100644 index 9237726..0000000 Binary files a/ios/App/Assets.xcassets/AppIcon.appiconset/icon-1024.png and /dev/null differ diff --git a/ios/App/Assets.xcassets/Contents.json b/ios/App/Assets.xcassets/Contents.json deleted file mode 100644 index f223229..0000000 --- a/ios/App/Assets.xcassets/Contents.json +++ /dev/null @@ -1 +0,0 @@ -{"info":{"author":"xcode","version":1}} diff --git a/ios/App/ContentView.swift b/ios/App/ContentView.swift deleted file mode 100644 index 4f90b9f..0000000 --- a/ios/App/ContentView.swift +++ /dev/null @@ -1,129 +0,0 @@ -import NetworkExtension -import SwiftUI - -struct ContentView: View { - @EnvironmentObject var vpn: VPNController - @State private var pasteText = "" - @State private var showImport = false - - var body: some View { - NavigationStack { - ScrollView { - VStack(spacing: 18) { - profileCard - if vpn.servers.count > 1 { serversCard } - powerButton - statusLine - optionsCard - Button { showImport = true } label: { - Label("Импорт ссылки phobos://", systemImage: "square.and.arrow.down") - .frame(maxWidth: .infinity).padding(.vertical, 12) - } - .buttonStyle(.borderedProminent).tint(.indigo) - Link(destination: URL(string: "https://speed.cloudflare.com/")!) { - Label("Проверить скорость", systemImage: "speedometer").frame(maxWidth: .infinity).padding(.vertical, 12) - } - .buttonStyle(.bordered) - if !vpn.message.isEmpty { - Text(vpn.message).font(.footnote).foregroundStyle(.secondary).multilineTextAlignment(.center) - } - } - .padding() - } - .navigationTitle("PCA Phobos VPN") - .alert("Ссылка из панели", isPresented: $showImport) { - TextField("phobos://…", text: $pasteText) - Button("Импорт") { vpn.importLink(pasteText); pasteText = "" } - Button("Из буфера") { vpn.importLink(UIPasteboard.general.string ?? "") } - Button("Отмена", role: .cancel) {} - } message: { - Text("В панели: клиент → 📱 → ссылка phobos://. Можно просто открыть ссылку на iPhone — профиль импортируется сам.") - } - } - } - - private var profileCard: some View { - VStack(alignment: .leading, spacing: 4) { - if let p = vpn.profile { - Text(vpn.name).font(.title3.bold()) - Text("Сервер: \(p.target ?? "?")").font(.subheadline).foregroundStyle(.secondary) - if let tcp = p.tcpPort { Text("Резервный TCP: порт \(tcp)").font(.caption).foregroundStyle(.secondary) } - } else { - Text("Профиль не импортирован").font(.headline) - Text("Откройте на iPhone ссылку phobos:// из панели PCA Phobos или вставьте её кнопкой ниже.") - .font(.subheadline).foregroundStyle(.secondary) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding().background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) - } - - private var serversCard: some View { - VStack(alignment: .leading, spacing: 0) { - Text("Серверы").font(.subheadline.bold()).padding(.bottom, 6) - ForEach(vpn.servers, id: \.self) { s in - let n = s["name"] ?? "" - HStack { - Image(systemName: n == vpn.name ? "checkmark.circle.fill" : "circle") - .foregroundStyle(n == vpn.name ? .green : .secondary) - Text(n) - Spacer() - if n != vpn.name { - Button(role: .destructive) { vpn.remove(n) } label: { Image(systemName: "trash") } - .buttonStyle(.borderless) - } - } - .padding(.vertical, 8) - .contentShape(Rectangle()) - .onTapGesture { vpn.select(n) } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding().background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) - } - - private var powerButton: some View { - Button(action: vpn.toggle) { - Text(vpn.isOn ? "Отключить" : "Подключить") - .font(.title2.bold()).foregroundStyle(.white) - .frame(width: 180, height: 180) - .background(Circle().fill(vpn.isOn ? Color.green : Color.indigo)) - } - .padding(.top, 8) - } - - private var statusLine: some View { - let text: String = { - switch vpn.status { - case .connected: return "🟢 VPN включён" + (vpn.transport.isEmpty ? "" : " · \(vpn.transport)") - case .connecting, .reasserting: return "🟡 Подключение…" - case .disconnecting: return "Отключение…" - default: return "⚪ VPN выключен" - } - }() - return Text(text).font(.headline) - } - - private var optionsCard: some View { - VStack(alignment: .leading, spacing: 12) { - Toggle("Российские сайты — напрямую, без VPN", isOn: $vpn.ruDirect) - VStack(alignment: .leading, spacing: 6) { - Text("Канал до сервера").font(.subheadline.bold()) - Picker("Канал", selection: $vpn.transportMode) { - Text("Авто").tag("auto"); Text("UDP").tag("udp"); Text("TCP").tag("tcp"); Text("Облако").tag("cloud") - } - .pickerStyle(.segmented) - if vpn.transportMode == "cloud" { - TextField("wss://phobos.имя.keenetic.pro/", text: $vpn.wsURL) - .textInputAutocapitalization(.never).autocorrectionDisabled().keyboardType(.URL) - .textFieldStyle(.roundedBorder).onSubmit { vpn.wsURL = vpn.wsURL.trimmingCharacters(in: .whitespaces) } - Text("Облако: телефон → облако KeenDNS → ваш Keenetic → сервер. Работает, когда до сервера напрямую не достучаться.") - .font(.caption).foregroundStyle(.secondary) - } - Text("Авто: если UDP режут, через 15 секунд переходит на TCP. Изменения применяются сразу.") - .font(.caption).foregroundStyle(.secondary) - } - } - .padding().background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) - } -} diff --git a/ios/App/Info.plist b/ios/App/Info.plist deleted file mode 100644 index 15ca370..0000000 --- a/ios/App/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - PCA Phobos - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleURLTypes - - - CFBundleURLName - com.bobyrev.pcaphobos.link - CFBundleURLSchemes - - phobos - - - - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - ITSAppUsesNonExemptEncryption - - UILaunchScreen - - UIRequiresFullScreen - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - - diff --git a/ios/App/PCAPhobos.entitlements b/ios/App/PCAPhobos.entitlements deleted file mode 100644 index ffab33e..0000000 --- a/ios/App/PCAPhobos.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - - diff --git a/ios/App/PCAPhobosApp.swift b/ios/App/PCAPhobosApp.swift deleted file mode 100644 index e68cdbd..0000000 --- a/ios/App/PCAPhobosApp.swift +++ /dev/null @@ -1,131 +0,0 @@ -import NetworkExtension -import SwiftUI - -@main -struct PCAPhobosApp: App { - @StateObject private var vpn = VPNController() - - var body: some Scene { - WindowGroup { - ContentView() - .environmentObject(vpn) - .onOpenURL { vpn.importLink($0.absoluteString) } // phobos:// from the panel - } - } -} - -/// Owns the NETunnelProviderManager: profile, options, connect/disconnect, live status. -@MainActor -final class VPNController: ObservableObject { - @Published var name = UserDefaults.standard.string(forKey: "name") ?? "" - @Published var conf = UserDefaults.standard.string(forKey: "conf") ?? "" - @Published var ruDirect = UserDefaults.standard.object(forKey: "ruDirect") as? Bool ?? true { didSet { saveOptions() } } - @Published var transportMode = UserDefaults.standard.string(forKey: "transport") ?? "auto" { didSet { saveOptions() } } - /// Channel «Облако»: wss:// of the user's Keenetic (empty -> "# pca-ws" from the link). - @Published var wsURL = UserDefaults.standard.string(forKey: "wsURL") ?? "" { didSet { saveOptions() } } - /// Imported servers: [["name": …, "conf": …]]; name/conf above mirror the selected one. - @Published var servers = UserDefaults.standard.array(forKey: "servers") as? [[String: String]] ?? [] - @Published var status: NEVPNStatus = .invalid - @Published var transport = "" - @Published var message = "" - - private var manager: NETunnelProviderManager? - private var timer: Timer? - - init() { - if servers.isEmpty && !conf.isEmpty { servers = [["name": name, "conf": conf]] } // pre-list single profile - Task { await load() } - NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: .main) { [weak self] _ in - Task { @MainActor in self?.status = self?.manager?.connection.status ?? .invalid } - } - timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in - Task { @MainActor in self?.pollTransport() } - } - } - - var profile: PhobosProfile? { conf.isEmpty ? nil : PhobosProfile(conf) } - var isOn: Bool { status == .connected || status == .connecting || status == .reasserting } - - func load() async { - let all = (try? await NETunnelProviderManager.loadAllFromPreferences()) ?? [] - manager = all.first - status = manager?.connection.status ?? .invalid - } - - func importLink(_ link: String) { - guard let parsed = PhobosProfile.fromLink(link) else { message = "Это не ссылка phobos:// из панели"; return } - servers.removeAll { $0["name"] == parsed.name } // same name = updated link - servers.append(["name": parsed.name, "conf": parsed.profile.raw]) - select(parsed.name) - message = "Профиль «\(name)» импортирован" - } - - func select(_ server: String) { - guard let s = servers.first(where: { $0["name"] == server }) else { return } - name = server - conf = s["conf"] ?? "" - persist() - Task { await save(reconnect: isOn) } - } - - func remove(_ server: String) { - servers.removeAll { $0["name"] == server } - if server == name { - if let next = servers.first?["name"] { select(next); return } - if isOn { manager?.connection.stopVPNTunnel() } - name = ""; conf = "" - } - persist() - } - - private func persist() { - UserDefaults.standard.set(servers, forKey: "servers") - UserDefaults.standard.set(conf, forKey: "conf") - UserDefaults.standard.set(name, forKey: "name") - } - - private func saveOptions() { - UserDefaults.standard.set(ruDirect, forKey: "ruDirect") - UserDefaults.standard.set(transportMode, forKey: "transport") - UserDefaults.standard.set(wsURL, forKey: "wsURL") - Task { await save(reconnect: isOn) } - } - - /// Write the VPN configuration to iOS settings (asks the user once). - func save(reconnect: Bool = false) async { - guard let profile else { return } - let m = manager ?? NETunnelProviderManager() - let proto = NETunnelProviderProtocol() - proto.providerBundleIdentifier = "com.bobyrev.pcaphobos.tunnel" - proto.serverAddress = profile.targetHost ?? "Phobos" - proto.providerConfiguration = ["conf": conf, "ruDirect": ruDirect, "transport": transportMode, "wsURL": wsURL] - m.protocolConfiguration = proto - m.localizedDescription = "PCA Phobos" - m.isEnabled = true - do { - try await m.saveToPreferences() - try await m.loadFromPreferences() - manager = m - if reconnect { m.connection.stopVPNTunnel(); try await Task.sleep(nanoseconds: 1_000_000_000); try m.connection.startVPNTunnel() } - } catch { - message = "Не удалось сохранить VPN: \(error.localizedDescription)" - } - } - - func toggle() { - Task { - if isOn { manager?.connection.stopVPNTunnel(); return } - guard profile != nil else { message = "Сначала импортируйте ссылку phobos:// из панели"; return } - await save() - do { try manager?.connection.startVPNTunnel() } catch { message = "Ошибка: \(error.localizedDescription)" } - } - } - - private func pollTransport() { - guard status == .connected, let session = manager?.connection as? NETunnelProviderSession else { transport = ""; return } - try? session.sendProviderMessage(Data()) { [weak self] data in - guard let data, let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } - Task { @MainActor in self?.transport = obj["transport"] as? String ?? "" } - } - } -} diff --git a/ios/PCAPhobos.xcodeproj/project.pbxproj b/ios/PCAPhobos.xcodeproj/project.pbxproj deleted file mode 100644 index 403c7af..0000000 --- a/ios/PCAPhobos.xcodeproj/project.pbxproj +++ /dev/null @@ -1,576 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 008DA398D56DFBDB16D97CE1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF14952A4263FF0E004A3F04 /* ContentView.swift */; }; - 08C4842C3185E04B2154C971 /* WireGuardKit in Frameworks */ = {isa = PBXBuildFile; productRef = FA829997FAB330F6EFA104C9 /* WireGuardKit */; }; - 11F2A6A8D281AB36B45D4603 /* TunnelConfiguration+WgQuickConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1AA6DED9B45949864A9B7D /* TunnelConfiguration+WgQuickConfig.swift */; }; - 1C05D29ACA95548374AD6FD9 /* logging.c in Sources */ = {isa = PBXBuildFile; fileRef = 9FC04650634F03E6DB09AB6B /* logging.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; - 38870650A7C2B1F0927A1230 /* wg-obfuscator.c in Sources */ = {isa = PBXBuildFile; fileRef = 1FADBB2A65AF77D4ECCAFE78 /* wg-obfuscator.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; - 4E4255B9E0F8626F6E209B0F /* String+ArrayConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7500F0B31797D49B5BBEC5CC /* String+ArrayConversion.swift */; }; - 56F35C68C6A387E499703CFF /* masking.c in Sources */ = {isa = PBXBuildFile; fileRef = CCE5C98CD905BDE16A8D83D4 /* masking.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; - 599057D9048B5FF1EA0E6000 /* WsLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83AB79BE0E361F4EE90875 /* WsLink.swift */; }; - 5F5D376513C0B27C45A3E7B6 /* ru_bypass.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2572D9A6F822B038D0652619 /* ru_bypass.txt */; }; - 78B74F81E0042D9AD94B7A6B /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E5C1673464AAC3EF81DD2FE /* config.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; - 7D8AB66421409CBFCCF2769A /* PhobosProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */; }; - 99F5DB52833E3B5505EEA727 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E36BA501FDCB2034F09415A /* PacketTunnelProvider.swift */; }; - B3BB8BE3409D63FEB59B5449 /* obfuscation.c in Sources */ = {isa = PBXBuildFile; fileRef = EF9115CA0D5EC52C8679A12A /* obfuscation.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; - C3C41D3C419E8D17F20A38B5 /* PCAPhobosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E470E13EF603CB26991683 /* PCAPhobosApp.swift */; }; - C6116E8B9CDFEE363BCBD2D6 /* PhobosTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - CBC338B91B5FCE1E207B36AC /* PhobosProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */; }; - D8CCC1E6CC8164EDD4E34D25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6359B8954E69147DA50EDB46 /* Assets.xcassets */; }; - DF6D62C526E908CA5CDDFCAE /* pcarelay.c in Sources */ = {isa = PBXBuildFile; fileRef = 211950F8776D3D14D7B9C49A /* pcarelay.c */; }; - E88105F9325A93041D617213 /* masking_stun.c in Sources */ = {isa = PBXBuildFile; fileRef = 05DD44245829A52D32B42AF8 /* masking_stun.c */; settings = {COMPILER_FLAGS = "-Dmain=wgobf_main -w"; }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - B9875EF5DEB6362919B43F22 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = E59D1112D2B128DF6FE7A1E9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 0FD066BC2C51A1E092914FE8; - remoteInfo = PhobosTunnel; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9FC25420ED5EF973CE140584 /* Embed Foundation Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - C6116E8B9CDFEE363BCBD2D6 /* PhobosTunnel.appex in Embed Foundation Extensions */, - ); - name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 027878C5E0654DB27FAE88A3 /* PCAPhobos.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = PCAPhobos.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 05DD44245829A52D32B42AF8 /* masking_stun.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = masking_stun.c; sourceTree = ""; }; - 0E5C1673464AAC3EF81DD2FE /* config.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = config.c; sourceTree = ""; }; - 1FADBB2A65AF77D4ECCAFE78 /* wg-obfuscator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "wg-obfuscator.c"; sourceTree = ""; }; - 211950F8776D3D14D7B9C49A /* pcarelay.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pcarelay.c; sourceTree = ""; }; - 2572D9A6F822B038D0652619 /* ru_bypass.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ru_bypass.txt; sourceTree = ""; }; - 2C1AA6DED9B45949864A9B7D /* TunnelConfiguration+WgQuickConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+WgQuickConfig.swift"; sourceTree = ""; }; - 6359B8954E69147DA50EDB46 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 722DE8EFC35A88BC68637FB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 7500F0B31797D49B5BBEC5CC /* String+ArrayConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ArrayConversion.swift"; sourceTree = ""; }; - 7E66B3AD4BB22D2AB5676E10 /* PCAPhobos.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PCAPhobos.entitlements; sourceTree = ""; }; - 8E36BA501FDCB2034F09415A /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; - 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhobosProfile.swift; sourceTree = ""; }; - 9FC04650634F03E6DB09AB6B /* logging.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = logging.c; sourceTree = ""; }; - A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = PhobosTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - B3E470E13EF603CB26991683 /* PCAPhobosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PCAPhobosApp.swift; sourceTree = ""; }; - CA6480536DA633DB94B6930F /* WireGuardKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WireGuardKit; path = WireGuardKit; sourceTree = SOURCE_ROOT; }; - CCE5C98CD905BDE16A8D83D4 /* masking.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = masking.c; sourceTree = ""; }; - EC83AB79BE0E361F4EE90875 /* WsLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WsLink.swift; sourceTree = ""; }; - EF9115CA0D5EC52C8679A12A /* obfuscation.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = obfuscation.c; sourceTree = ""; }; - FF14952A4263FF0E004A3F04 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - D366EA9C7C0EC88CAE5EA47E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 08C4842C3185E04B2154C971 /* WireGuardKit in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 1EBBB94A186F7080FE3609C8 /* Products */ = { - isa = PBXGroup; - children = ( - 027878C5E0654DB27FAE88A3 /* PCAPhobos.app */, - A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */, - ); - name = Products; - sourceTree = ""; - }; - 2500D202DC9E96E7BBCE29B1 /* Tunnel */ = { - isa = PBXGroup; - children = ( - 8E36BA501FDCB2034F09415A /* PacketTunnelProvider.swift */, - 211950F8776D3D14D7B9C49A /* pcarelay.c */, - 2572D9A6F822B038D0652619 /* ru_bypass.txt */, - 7500F0B31797D49B5BBEC5CC /* String+ArrayConversion.swift */, - 2C1AA6DED9B45949864A9B7D /* TunnelConfiguration+WgQuickConfig.swift */, - EC83AB79BE0E361F4EE90875 /* WsLink.swift */, - ); - path = Tunnel; - sourceTree = ""; - }; - 527268DF962A8B7EC120C536 /* obfuscator */ = { - isa = PBXGroup; - children = ( - 0E5C1673464AAC3EF81DD2FE /* config.c */, - 9FC04650634F03E6DB09AB6B /* logging.c */, - 05DD44245829A52D32B42AF8 /* masking_stun.c */, - CCE5C98CD905BDE16A8D83D4 /* masking.c */, - EF9115CA0D5EC52C8679A12A /* obfuscation.c */, - 1FADBB2A65AF77D4ECCAFE78 /* wg-obfuscator.c */, - ); - name = obfuscator; - path = Tunnel/obfuscator; - sourceTree = ""; - }; - D4CB33FA621F53C8F82ED115 /* Packages */ = { - isa = PBXGroup; - children = ( - CA6480536DA633DB94B6930F /* WireGuardKit */, - ); - name = Packages; - sourceTree = ""; - }; - DD1FD95C0B2DE48623872799 /* App */ = { - isa = PBXGroup; - children = ( - 6359B8954E69147DA50EDB46 /* Assets.xcassets */, - FF14952A4263FF0E004A3F04 /* ContentView.swift */, - 722DE8EFC35A88BC68637FB4 /* Info.plist */, - 7E66B3AD4BB22D2AB5676E10 /* PCAPhobos.entitlements */, - B3E470E13EF603CB26991683 /* PCAPhobosApp.swift */, - ); - path = App; - sourceTree = ""; - }; - E31449B836D353F74991D905 = { - isa = PBXGroup; - children = ( - DD1FD95C0B2DE48623872799 /* App */, - 527268DF962A8B7EC120C536 /* obfuscator */, - D4CB33FA621F53C8F82ED115 /* Packages */, - F6FC482145D1EFAD5A15F63B /* Shared */, - 2500D202DC9E96E7BBCE29B1 /* Tunnel */, - 1EBBB94A186F7080FE3609C8 /* Products */, - ); - sourceTree = ""; - }; - F6FC482145D1EFAD5A15F63B /* Shared */ = { - isa = PBXGroup; - children = ( - 98DC1765DEC5F326E2B5F9C4 /* PhobosProfile.swift */, - ); - path = Shared; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 0FD066BC2C51A1E092914FE8 /* PhobosTunnel */ = { - isa = PBXNativeTarget; - buildConfigurationList = 329F6A5A13B95B1525F94841 /* Build configuration list for PBXNativeTarget "PhobosTunnel" */; - buildPhases = ( - 173BA4C39EE769A34EBB19CB /* Sources */, - A5352C1E8C48FFB2CCEC38F3 /* Resources */, - D366EA9C7C0EC88CAE5EA47E /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = PhobosTunnel; - packageProductDependencies = ( - FA829997FAB330F6EFA104C9 /* WireGuardKit */, - ); - productName = PhobosTunnel; - productReference = A81F96BF4131FDBB816623BE /* PhobosTunnel.appex */; - productType = "com.apple.product-type.app-extension"; - }; - E1F7E69029753CF102C6CE2E /* PCAPhobos */ = { - isa = PBXNativeTarget; - buildConfigurationList = 61EF055D39FD21A29FA039D5 /* Build configuration list for PBXNativeTarget "PCAPhobos" */; - buildPhases = ( - F83DEE91B85335727CA6B39E /* Sources */, - D18596DD6D613F6BC9647644 /* Resources */, - 9FC25420ED5EF973CE140584 /* Embed Foundation Extensions */, - ); - buildRules = ( - ); - dependencies = ( - 0B334C04E5AD898D8736FFAA /* PBXTargetDependency */, - ); - name = PCAPhobos; - packageProductDependencies = ( - ); - productName = PCAPhobos; - productReference = 027878C5E0654DB27FAE88A3 /* PCAPhobos.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - E59D1112D2B128DF6FE7A1E9 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - TargetAttributes = { - 0FD066BC2C51A1E092914FE8 = { - DevelopmentTeam = Q5FKU39TUA; - ProvisioningStyle = Automatic; - }; - E1F7E69029753CF102C6CE2E = { - DevelopmentTeam = Q5FKU39TUA; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = F73680B923D76C65D03B442C /* Build configuration list for PBXProject "PCAPhobos" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = E31449B836D353F74991D905; - minimizedProjectReferenceProxies = 1; - packageReferences = ( - 9F8EC62EA6724F5FA6B8BAE0 /* XCLocalSwiftPackageReference "WireGuardKit" */, - ); - preferredProjectObjectVersion = 77; - productRefGroup = 1EBBB94A186F7080FE3609C8 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - E1F7E69029753CF102C6CE2E /* PCAPhobos */, - 0FD066BC2C51A1E092914FE8 /* PhobosTunnel */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - A5352C1E8C48FFB2CCEC38F3 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 5F5D376513C0B27C45A3E7B6 /* ru_bypass.txt in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D18596DD6D613F6BC9647644 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D8CCC1E6CC8164EDD4E34D25 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 173BA4C39EE769A34EBB19CB /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 99F5DB52833E3B5505EEA727 /* PacketTunnelProvider.swift in Sources */, - 7D8AB66421409CBFCCF2769A /* PhobosProfile.swift in Sources */, - 4E4255B9E0F8626F6E209B0F /* String+ArrayConversion.swift in Sources */, - 11F2A6A8D281AB36B45D4603 /* TunnelConfiguration+WgQuickConfig.swift in Sources */, - 599057D9048B5FF1EA0E6000 /* WsLink.swift in Sources */, - 78B74F81E0042D9AD94B7A6B /* config.c in Sources */, - 1C05D29ACA95548374AD6FD9 /* logging.c in Sources */, - 56F35C68C6A387E499703CFF /* masking.c in Sources */, - E88105F9325A93041D617213 /* masking_stun.c in Sources */, - B3BB8BE3409D63FEB59B5449 /* obfuscation.c in Sources */, - DF6D62C526E908CA5CDDFCAE /* pcarelay.c in Sources */, - 38870650A7C2B1F0927A1230 /* wg-obfuscator.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F83DEE91B85335727CA6B39E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 008DA398D56DFBDB16D97CE1 /* ContentView.swift in Sources */, - C3C41D3C419E8D17F20A38B5 /* PCAPhobosApp.swift in Sources */, - CBC338B91B5FCE1E207B36AC /* PhobosProfile.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 0B334C04E5AD898D8736FFAA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 0FD066BC2C51A1E092914FE8 /* PhobosTunnel */; - targetProxy = B9875EF5DEB6362919B43F22 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 35473CF1EA2AAAC350F346D0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = App/PCAPhobos.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - INFOPLIST_FILE = App/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 62739003B63EE4EE3B3FF1FB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = Tunnel/PhobosTunnel.entitlements; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; - HEADER_SEARCH_PATHS = ( - "$(PROJECT_DIR)/Tunnel", - "$(PROJECT_DIR)/Tunnel/obfuscator", - ); - INFOPLIST_FILE = Tunnel/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(PROJECT_DIR)/WireGuardGo/iphoneos", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(PROJECT_DIR)/WireGuardGo/iphonesimulator", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos.tunnel; - SDKROOT = iphoneos; - SWIFT_OBJC_BRIDGING_HEADER = "Tunnel/Tunnel-Bridging-Header.h"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 7ABA8220043DEF78154F9F38 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 6; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = Q5FKU39TUA; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MARKETING_VERSION = 1.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - B5D2F9B70DD37D3A57737EED /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 6; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = Q5FKU39TUA; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MARKETING_VERSION = 1.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - D0AEA32C3C7BFB88918AB206 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = Tunnel/PhobosTunnel.entitlements; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; - HEADER_SEARCH_PATHS = ( - "$(PROJECT_DIR)/Tunnel", - "$(PROJECT_DIR)/Tunnel/obfuscator", - ); - INFOPLIST_FILE = Tunnel/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(PROJECT_DIR)/WireGuardGo/iphoneos", - ); - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(PROJECT_DIR)/WireGuardGo/iphonesimulator", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos.tunnel; - SDKROOT = iphoneos; - SWIFT_OBJC_BRIDGING_HEADER = "Tunnel/Tunnel-Bridging-Header.h"; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - D42BF07983FD43EBB6D84762 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = App/PCAPhobos.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - INFOPLIST_FILE = App/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.bobyrev.pcaphobos; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 329F6A5A13B95B1525F94841 /* Build configuration list for PBXNativeTarget "PhobosTunnel" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D0AEA32C3C7BFB88918AB206 /* Debug */, - 62739003B63EE4EE3B3FF1FB /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; - 61EF055D39FD21A29FA039D5 /* Build configuration list for PBXNativeTarget "PCAPhobos" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D42BF07983FD43EBB6D84762 /* Debug */, - 35473CF1EA2AAAC350F346D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; - F73680B923D76C65D03B442C /* Build configuration list for PBXProject "PCAPhobos" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B5D2F9B70DD37D3A57737EED /* Debug */, - 7ABA8220043DEF78154F9F38 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; -/* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 9F8EC62EA6724F5FA6B8BAE0 /* XCLocalSwiftPackageReference "WireGuardKit" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = WireGuardKit; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - FA829997FAB330F6EFA104C9 /* WireGuardKit */ = { - isa = XCSwiftPackageProductDependency; - productName = WireGuardKit; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = E59D1112D2B128DF6FE7A1E9 /* Project object */; -} diff --git a/ios/PCAPhobos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/PCAPhobos.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/ios/PCAPhobos.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ios/README.md b/ios/README.md deleted file mode 100644 index b92bf3d..0000000 --- a/ios/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# PCA Phobos VPN — iPhone - -То же, что Android-клиент: импорт `phobos://` из панели, обфускация (wg-obfuscator внутри -расширения), **российские сайты напрямую**, канал **Авто/UDP/TCP/Облако** (при блокировке UDP — сам на TCP; «Облако» — через KeenDNS своего Keenetic, см. `../wsgw/README.md`). -Выбор отдельных приложений на iOS невозможен (Apple даёт это только через MDM) — делим по адресам. - -Устройство: -- `App/` — SwiftUI-приложение (профиль, кнопка, настройки), `Shared/PhobosProfile.swift` — разбор ссылки. -- `Tunnel/` — Packet Tunnel: WireGuardKit → 127.0.0.1:13255 wg-obfuscator (`-Dmain=wgobf_main`, один - экземпляр: iOS запрещает fork) → 127.0.0.1:13258 `pcarelay.c` → сервер по UDP или TCP (формат udptcp), - либо «Облако»: пакеты из pcarelay в `WsLink.swift` (URLSessionWebSocketTask) → wss://поддомен роутера → wsgw → сервер. -- `WireGuardKit/` — wireguard-apple (MIT), `WireGuardGo//libwg-go.a` — собран `make` (iphoneos) и - `go build -buildmode c-archive` с `-target arm64-apple-ios15.0-simulator` (симулятор). - -Сборка: `xcodegen generate` → `PCAPhobos.xcodeproj`. Проверка без телефона: `Tests/ProfileCheck`, режим «Облако» релея — `Tests/RelayCheck/main.c`. - -## Установка на iPhone (нужен ПЛАТНЫЙ Apple Developer, $99/год) -Network Extension (packet-tunnel-provider) Apple выдаёт только платным аккаунтам — с бесплатным -Xcode не подпишет даже одно приложение. Когда аккаунт есть: -1. В `project.yml` вписать `DEVELOPMENT_TEAM: `, `xcodegen generate`. -2. Xcode → Signing: включить capability Network Extensions (Packet Tunnel) для обоих таргетов. -3. iPhone по кабелю → Run, либо Product → Archive → TestFlight (тестировщики по e-mail, 90 дней). -4. В панели клиента → 📱 → открыть ссылку `phobos://` на iPhone → «Открыть в PCA Phobos» → Подключить - → разрешить добавление VPN. - -Проверено 23.10.2026: сборка под iphoneos и симулятор, UI и импорт реальной ссылки в симуляторе. -Сам туннель в симуляторе iOS не работает — проверяется только на телефоне. diff --git a/ios/Shared/PhobosProfile.swift b/ios/Shared/PhobosProfile.swift deleted file mode 100644 index a6f1978..0000000 --- a/ios/Shared/PhobosProfile.swift +++ /dev/null @@ -1,101 +0,0 @@ -import Foundation - -/// Parsed phobos:// payload from the PCA Phobos panel: -/// [Interface] + [Peer] (WireGuard) + [instance] (wg-obfuscator) + "# pca-tcp = ". -struct PhobosProfile { - let raw: String - - init(_ raw: String) { self.raw = raw } - - /// phobos://#name - static func fromLink(_ link: String) -> (profile: PhobosProfile, name: String)? { - var s = link.trimmingCharacters(in: .whitespacesAndNewlines) - guard s.hasPrefix("phobos://") else { return nil } - s.removeFirst("phobos://".count) - var name = "Phobos" - if let hash = s.firstIndex(of: "#") { - name = String(s[s.index(after: hash)...]).removingPercentEncoding ?? name - s = String(s[.. [(key: String, line: String)] { - var inSection = false - var out: [(String, String)] = [] - for rawLine in raw.split(separator: "\n") { - let line = rawLine.trimmingCharacters(in: .whitespaces) - if line.hasPrefix("[") && line.hasSuffix("]") { inSection = line.lowercased() == header.lowercased(); continue } - guard inSection, !line.hasPrefix("#"), let eq = line.firstIndex(of: "=") else { continue } - let value = line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces) - if value.lowercased() == "none" { continue } - out.append((line[.. String? { - lines(header).first { $0.key == key }.map { line in - line.line[line.line.index(after: line.line.firstIndex(of: "=")!)...].trimmingCharacters(in: .whitespaces) - } - } - - var target: String? { value("[instance]", "target") } - var targetHost: String? { target.map { String($0[..<($0.lastIndex(of: ":") ?? $0.endIndex)]) } } - var targetPort: Int? { target.flatMap { $0.split(separator: ":").last.flatMap { Int($0) } } } - - /// "# pca-tcp = 443" written by the panel when the server has the TCP fallback. - var tcpPort: Int? { - for rawLine in raw.split(separator: "\n") { - let line = rawLine.trimmingCharacters(in: .whitespaces) - if line.hasPrefix("#"), line.contains("pca-tcp"), let eq = line.firstIndex(of: "=") { - return Int(line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces)) - } - } - return nil - } - - /// "# pca-ws = wss://…" — the user's Keenetic (KeenDNS cloud subdomain -> wsgw), channel «Облако». - var wsURL: String? { - for rawLine in raw.split(separator: "\n") { - let line = rawLine.trimmingCharacters(in: .whitespaces) - if line.hasPrefix("#"), line.contains("pca-ws"), let eq = line.firstIndex(of: "=") { - return line[line.index(after: eq)...].trimmingCharacters(in: .whitespaces) - } - } - return nil - } - - func obfuscatorConfig(listen: Int, target: String) -> String { - var out = "[instance]\nsource-if = 127.0.0.1\nsource-lport = \(listen)\ntarget = \(target)\n" - for item in lines("[instance]") where !["source-if", "source-lport", "target", "verbose"].contains(item.key) { - out += item.line + "\n" - } - return out + "verbose = INFO\n" - } - - func wireGuardConfig(endpoint: String, allowedIPs: String?) -> String { - var out = "[Interface]\n" - for item in lines("[Interface]") where !item.key.hasSuffix("applications") && item.key != "mtu" { out += item.line + "\n" } - // ponytail: obfuscator masking adds bytes; the panel's default 1420 stalls on mobile networks, - // so 1420/missing -> 1280, any other MTU in the link wins (set per client in the panel) - let linkMTU = lines("[Interface]").first { $0.key == "mtu" } - .flatMap { Int($0.line.split(separator: "=").last?.trimmingCharacters(in: .whitespaces) ?? "") } - let mtu = (linkMTU == nil || linkMTU == 1420) ? 1280 : linkMTU! - out += "MTU = \(mtu)\n" - out += "\n[Peer]\n" - for item in lines("[Peer]") { - switch item.key { - case "endpoint": out += "Endpoint = \(endpoint)\n" - case "allowedips" where allowedIPs != nil: out += "AllowedIPs = \(allowedIPs!)\n" - default: out += item.line + "\n" - } - } - return out - } -} diff --git a/ios/Tests/ProfileCheck/main.swift b/ios/Tests/ProfileCheck/main.swift deleted file mode 100644 index 9e9e62a..0000000 --- a/ios/Tests/ProfileCheck/main.swift +++ /dev/null @@ -1,41 +0,0 @@ -// Self-check of phobos:// parsing (no device needed): -// cd ios/Tests/ProfileCheck && xcrun swiftc -o /tmp/pc ../../Shared/PhobosProfile.swift main.swift && /tmp/pc -import Foundation -let conf = """ -[Interface] -PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -Address = 10.25.0.9/32 -DNS = 1.1.1.1 -MTU = 1420 - -[Peer] -PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= -PresharedKey = none -Endpoint = 127.0.0.1:51820 -AllowedIPs = 0.0.0.0/0, ::/0 -PersistentKeepalive = 25 - -[instance] -source-if = 127.0.0.1 -source-lport = 51820 -target = 203.0.113.10:2083 -key = testkey -masking = STUN -verbose = INFO -idle-timeout = 300 -max-dummy = 4 -# pca-tcp = 443 -""" -let link = "phobos://" + Data(conf.utf8).base64EncodedString() - .replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "=", with: "") + "#demo" -let (p, name) = PhobosProfile.fromLink(link)! -precondition(name == "demo" && p.targetHost == "203.0.113.10" && p.targetPort == 2083 && p.tcpPort == 443) -let obf = p.obfuscatorConfig(listen: 13255, target: "127.0.0.1:13258") -precondition(obf.contains("target = 127.0.0.1:13258") && obf.contains("key = testkey") && !obf.contains("203.0.113.10")) -let wg = p.wireGuardConfig(endpoint: "127.0.0.1:13255", allowedIPs: "1.0.0.0/8") -precondition(wg.contains("Endpoint = 127.0.0.1:13255") && wg.contains("AllowedIPs = 1.0.0.0/8") && !wg.contains("= none")) -precondition(wg.contains("MTU = 1280") && !wg.contains("1420")) // panel default -> 1280 -let custom = PhobosProfile.fromLink("phobos://" + Data(conf.replacingOccurrences(of: "MTU = 1420", with: "MTU = 1300").utf8).base64EncodedString())!.0 -precondition(custom.wireGuardConfig(endpoint: "e", allowedIPs: nil).contains("MTU = 1300")) // link MTU wins -print("profile_check ok") diff --git a/ios/Tests/RelayCheck/main.c b/ios/Tests/RelayCheck/main.c deleted file mode 100644 index afb03fd..0000000 --- a/ios/Tests/RelayCheck/main.c +++ /dev/null @@ -1,22 +0,0 @@ -/* Mode «Облако» of pcarelay without a phone: cc -I Tunnel -o /tmp/rc Tests/RelayCheck/main.c Tunnel/pcarelay.c && /tmp/rc */ -#include "pcarelay.h" -#include -#include -#include -#include -#include -int wgobf_main(int c, char **v) { return 0; } -static void out(const unsigned char *p, int n) { unsigned char r[64]; memcpy(r, p, n); r[0] = 'R'; pcarelay_ws_in(r, n); } -int main(void) { - pcarelay_start(13299, "127.0.0.1", 9, 0); - pcarelay_use_ws(out); - usleep(200000); - assert(pcarelay_is_ws() && !pcarelay_is_tcp()); - int s = socket(AF_INET, SOCK_DGRAM, 0); - struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = htons(13299), .sin_addr.s_addr = htonl(INADDR_LOOPBACK)}; - struct timeval tv = {2, 0}; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); - sendto(s, "ping", 4, 0, (struct sockaddr *)&a, sizeof a); - char b[16] = {0}; int n = recv(s, b, sizeof b, 0); - assert(n == 4 && !memcmp(b, "Ring", 4) && pcarelay_rx_bytes() == 4); - puts("pcarelay ws: ok"); -} diff --git a/ios/Tunnel/Info.plist b/ios/Tunnel/Info.plist deleted file mode 100644 index be6b352..0000000 --- a/ios/Tunnel/Info.plist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - PhobosTunnel - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - XPC! - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSExtension - - NSExtensionPointIdentifier - com.apple.networkextension.packet-tunnel - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).PacketTunnelProvider - - - diff --git a/ios/Tunnel/PacketTunnelProvider.swift b/ios/Tunnel/PacketTunnelProvider.swift deleted file mode 100644 index 2c8f7f7..0000000 --- a/ios/Tunnel/PacketTunnelProvider.swift +++ /dev/null @@ -1,107 +0,0 @@ -import Foundation -import NetworkExtension -import WireGuardKit - -/// PCA Phobos packet tunnel: -/// WireGuard (userspace) -> 127.0.0.1:13255 wg-obfuscator -> 127.0.0.1:13258 pcarelay -> server (UDP or TCP). -/// providerConfiguration: "conf" (phobos:// payload), "ruDirect" (Bool), "transport" ("auto"|"udp"|"tcp"|"cloud"), "wsURL". -/// "cloud": pcarelay hands packets to WsLink -> wss:/// -> wsgw on the router -> server UDP. -final class PacketTunnelProvider: NEPacketTunnelProvider { - private static let wgPort = 13255, relayPort = 13258 - private static var obfuscatorStarted = false // one wg-obfuscator per process (it forks for more) - - private lazy var adapter = WireGuardAdapter(with: self) { _, message in NSLog("wg: %@", message) } - - override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { - let pc = (protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration ?? [:] - guard let conf = pc["conf"] as? String, !conf.isEmpty else { - completionHandler(NSError(domain: "PCAPhobos", code: 1, userInfo: [NSLocalizedDescriptionKey: "нет профиля"])) - return - } - let ruDirect = pc["ruDirect"] as? Bool ?? true - let transport = pc["transport"] as? String ?? "auto" - let profile = PhobosProfile(conf) - - guard let host = profile.targetHost, let udpPort = profile.targetPort else { - completionHandler(NSError(domain: "PCAPhobos", code: 2, userInfo: [NSLocalizedDescriptionKey: "в профиле нет адреса сервера"])) - return - } - let tcpPort = profile.tcpPort ?? 0 - if transport == "tcp" && tcpPort == 0 { - completionHandler(NSError(domain: "PCAPhobos", code: 3, userInfo: [NSLocalizedDescriptionKey: "на сервере нет TCP-канала"])) - return - } - - let wsURL = (pc["wsURL"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? profile.wsURL - if transport == "cloud" { - guard let s = wsURL, let url = URL(string: s) else { - completionHandler(NSError(domain: "PCAPhobos", code: 4, userInfo: [NSLocalizedDescriptionKey: "не задан адрес роутера (wss://…) для канала «Облако»"])) - return - } - WsLink.shared.start(url) - pcarelay_use_ws { p, n in WsLink.shared.send(p, n) } - } else { - pcarelay_use_tcp(transport == "tcp" ? 1 : 0) - } - if !Self.obfuscatorStarted { - Self.obfuscatorStarted = true - pcarelay_start(Int32(Self.relayPort), host, Int32(udpPort), Int32(tcpPort)) - let path = NSTemporaryDirectory() + "obf.conf" - try? profile.obfuscatorConfig(listen: Self.wgPort, target: "127.0.0.1:\(Self.relayPort)") - .write(toFile: path, atomically: true, encoding: .utf8) - Thread.detachNewThread { - var args: [UnsafeMutablePointer?] = ["wg-obfuscator", "-c", path].map { strdup($0) } - _ = wgobf_main(Int32(args.count), &args) - } - } - - // full tunnel; Russian networks (all sizes, incl. CDN /24s) are excluded routes when ruDirect - PhobosRoutes.excludedIPv4 = ruDirect ? Self.ruNetworks() : [] - let wgText = profile.wireGuardConfig(endpoint: "127.0.0.1:\(Self.wgPort)", allowedIPs: nil) - let tunnelConfig: TunnelConfiguration - do { - tunnelConfig = try TunnelConfiguration(fromWgQuickConfig: wgText, called: "phobos") - } catch { - completionHandler(error) - return - } - adapter.start(tunnelConfiguration: tunnelConfig) { [weak self] error in - completionHandler(error) - if error == nil && transport == "auto" && tcpPort != 0 { self?.watchUdp() } - } - } - - /// Auto: nothing came back over UDP within 15 s -> UDP is cut, move the relay to TCP. - private func watchUdp() { - DispatchQueue.global().asyncAfter(deadline: .now() + 15) { - if pcarelay_is_tcp() == 0 && pcarelay_rx_bytes() == 0 { - NSLog("PCAPhobos: no UDP answer in 15 s -> TCP") - pcarelay_use_tcp(1) - } - } - } - - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - adapter.stop { _ in - completionHandler() - // the obfuscator thread cannot be stopped cleanly; a fresh process starts next time - exit(0) - } - } - - /// App asks for status: {"transport": "UDP"|"TCP", "rx": bytes} - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - let status: [String: Any] = ["transport": pcarelay_is_ws() != 0 ? "Облако" : pcarelay_is_tcp() != 0 ? "TCP" : "UDP", - "rx": pcarelay_rx_bytes()] - completionHandler?(try? JSONSerialization.data(withJSONObject: status)) - } - - /// ru_bypass.txt: Russian IPv4 networks (comma-separated). IPv6 stays fully in the tunnel, - /// otherwise blocked sites leak over carrier IPv6. - private static func ruNetworks() -> [String] { - guard let url = Bundle.main.url(forResource: "ru_bypass", withExtension: "txt"), - let text = try? String(contentsOf: url, encoding: .utf8), - let line = text.split(separator: "\n").first(where: { !$0.hasPrefix("#") && $0.contains("/") }) else { return [] } - return line.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) } - } -} diff --git a/ios/Tunnel/PhobosTunnel.entitlements b/ios/Tunnel/PhobosTunnel.entitlements deleted file mode 100644 index ffab33e..0000000 --- a/ios/Tunnel/PhobosTunnel.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - - diff --git a/ios/Tunnel/String+ArrayConversion.swift b/ios/Tunnel/String+ArrayConversion.swift deleted file mode 100644 index 97984f8..0000000 --- a/ios/Tunnel/String+ArrayConversion.swift +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation - -extension String { - - func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] { - return split(separator: separator) - .map { - if let charSet = trimmingCharacters { - return $0.trimmingCharacters(in: charSet) - } else { - return String($0) - } - } - } - -} - -extension Optional where Wrapped == String { - - func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] { - switch self { - case .none: - return [] - case .some(let wrapped): - return wrapped.splitToArray(separator: separator, trimmingCharacters: trimmingCharacters) - } - } - -} diff --git a/ios/Tunnel/Tunnel-Bridging-Header.h b/ios/Tunnel/Tunnel-Bridging-Header.h deleted file mode 100644 index 8e6dc13..0000000 --- a/ios/Tunnel/Tunnel-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#include "pcarelay.h" diff --git a/ios/Tunnel/TunnelConfiguration+WgQuickConfig.swift b/ios/Tunnel/TunnelConfiguration+WgQuickConfig.swift deleted file mode 100644 index d8cc11e..0000000 --- a/ios/Tunnel/TunnelConfiguration+WgQuickConfig.swift +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import WireGuardKit - -extension TunnelConfiguration { - - enum ParserState { - case inInterfaceSection - case inPeerSection - case notInASection - } - - enum ParseError: Error { - case invalidLine(String.SubSequence) - case noInterface - case multipleInterfaces - case interfaceHasNoPrivateKey - case interfaceHasInvalidPrivateKey(String) - case interfaceHasInvalidListenPort(String) - case interfaceHasInvalidAddress(String) - case interfaceHasInvalidDNS(String) - case interfaceHasInvalidMTU(String) - case interfaceHasUnrecognizedKey(String) - case peerHasNoPublicKey - case peerHasInvalidPublicKey(String) - case peerHasInvalidPreSharedKey(String) - case peerHasInvalidAllowedIP(String) - case peerHasInvalidEndpoint(String) - case peerHasInvalidPersistentKeepAlive(String) - case peerHasInvalidTransferBytes(String) - case peerHasInvalidLastHandshakeTime(String) - case peerHasUnrecognizedKey(String) - case multiplePeersWithSamePublicKey - case multipleEntriesForKey(String) - } - - convenience init(fromWgQuickConfig wgQuickConfig: String, called name: String? = nil) throws { - var interfaceConfiguration: InterfaceConfiguration? - var peerConfigurations = [PeerConfiguration]() - - let lines = wgQuickConfig.split { $0.isNewline } - - var parserState = ParserState.notInASection - var attributes = [String: String]() - - for (lineIndex, line) in lines.enumerated() { - var trimmedLine: String - if let commentRange = line.range(of: "#") { - trimmedLine = String(line[.. = ["privatekey", "listenport", "address", "dns", "mtu"] - let peerSectionKeys: Set = ["publickey", "presharedkey", "allowedips", "endpoint", "persistentkeepalive"] - if parserState == .inInterfaceSection { - guard interfaceSectionKeys.contains(key) else { - throw ParseError.interfaceHasUnrecognizedKey(keyWithCase) - } - } else if parserState == .inPeerSection { - guard peerSectionKeys.contains(key) else { - throw ParseError.peerHasUnrecognizedKey(keyWithCase) - } - } - } else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" { - throw ParseError.invalidLine(line) - } - } - - let isLastLine = lineIndex == lines.count - 1 - - if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { - // Previous section has ended; process the attributes collected so far - if parserState == .inInterfaceSection { - let interface = try TunnelConfiguration.collate(interfaceAttributes: attributes) - guard interfaceConfiguration == nil else { throw ParseError.multipleInterfaces } - interfaceConfiguration = interface - } else if parserState == .inPeerSection { - let peer = try TunnelConfiguration.collate(peerAttributes: attributes) - peerConfigurations.append(peer) - } - } - - if lowercasedLine == "[interface]" { - parserState = .inInterfaceSection - attributes.removeAll() - } else if lowercasedLine == "[peer]" { - parserState = .inPeerSection - attributes.removeAll() - } - } - - let peerPublicKeysArray = peerConfigurations.map { $0.publicKey } - let peerPublicKeysSet = Set(peerPublicKeysArray) - if peerPublicKeysArray.count != peerPublicKeysSet.count { - throw ParseError.multiplePeersWithSamePublicKey - } - - if let interfaceConfiguration = interfaceConfiguration { - self.init(name: name, interface: interfaceConfiguration, peers: peerConfigurations) - } else { - throw ParseError.noInterface - } - } - - func asWgQuickConfig() -> String { - var output = "[Interface]\n" - output.append("PrivateKey = \(interface.privateKey.base64Key)\n") - if let listenPort = interface.listenPort { - output.append("ListenPort = \(listenPort)\n") - } - if !interface.addresses.isEmpty { - let addressString = interface.addresses.map { $0.stringRepresentation }.joined(separator: ", ") - output.append("Address = \(addressString)\n") - } - if !interface.dns.isEmpty || !interface.dnsSearch.isEmpty { - var dnsLine = interface.dns.map { $0.stringRepresentation } - dnsLine.append(contentsOf: interface.dnsSearch) - let dnsString = dnsLine.joined(separator: ", ") - output.append("DNS = \(dnsString)\n") - } - if let mtu = interface.mtu { - output.append("MTU = \(mtu)\n") - } - - for peer in peers { - output.append("\n[Peer]\n") - output.append("PublicKey = \(peer.publicKey.base64Key)\n") - if let preSharedKey = peer.preSharedKey?.base64Key { - output.append("PresharedKey = \(preSharedKey)\n") - } - if !peer.allowedIPs.isEmpty { - let allowedIPsString = peer.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") - output.append("AllowedIPs = \(allowedIPsString)\n") - } - if let endpoint = peer.endpoint { - output.append("Endpoint = \(endpoint.stringRepresentation)\n") - } - if let persistentKeepAlive = peer.persistentKeepAlive { - output.append("PersistentKeepalive = \(persistentKeepAlive)\n") - } - } - - return output - } - - private static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration { - guard let privateKeyString = attributes["privatekey"] else { - throw ParseError.interfaceHasNoPrivateKey - } - guard let privateKey = PrivateKey(base64Key: privateKeyString) else { - throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString) - } - var interface = InterfaceConfiguration(privateKey: privateKey) - if let listenPortString = attributes["listenport"] { - guard let listenPort = UInt16(listenPortString) else { - throw ParseError.interfaceHasInvalidListenPort(listenPortString) - } - interface.listenPort = listenPort - } - if let addressesString = attributes["address"] { - var addresses = [IPAddressRange]() - for addressString in addressesString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { - guard let address = IPAddressRange(from: addressString) else { - throw ParseError.interfaceHasInvalidAddress(addressString) - } - addresses.append(address) - } - interface.addresses = addresses - } - if let dnsString = attributes["dns"] { - var dnsServers = [DNSServer]() - var dnsSearch = [String]() - for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { - if let dnsServer = DNSServer(from: dnsServerString) { - dnsServers.append(dnsServer) - } else { - dnsSearch.append(dnsServerString) - } - } - interface.dns = dnsServers - interface.dnsSearch = dnsSearch - } - if let mtuString = attributes["mtu"] { - guard let mtu = UInt16(mtuString) else { - throw ParseError.interfaceHasInvalidMTU(mtuString) - } - interface.mtu = mtu - } - return interface - } - - private static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration { - guard let publicKeyString = attributes["publickey"] else { - throw ParseError.peerHasNoPublicKey - } - guard let publicKey = PublicKey(base64Key: publicKeyString) else { - throw ParseError.peerHasInvalidPublicKey(publicKeyString) - } - var peer = PeerConfiguration(publicKey: publicKey) - if let preSharedKeyString = attributes["presharedkey"] { - guard let preSharedKey = PreSharedKey(base64Key: preSharedKeyString) else { - throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString) - } - peer.preSharedKey = preSharedKey - } - if let allowedIPsString = attributes["allowedips"] { - var allowedIPs = [IPAddressRange]() - for allowedIPString in allowedIPsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { - guard let allowedIP = IPAddressRange(from: allowedIPString) else { - throw ParseError.peerHasInvalidAllowedIP(allowedIPString) - } - allowedIPs.append(allowedIP) - } - peer.allowedIPs = allowedIPs - } - if let endpointString = attributes["endpoint"] { - guard let endpoint = Endpoint(from: endpointString) else { - throw ParseError.peerHasInvalidEndpoint(endpointString) - } - peer.endpoint = endpoint - } - if let persistentKeepAliveString = attributes["persistentkeepalive"] { - guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { - throw ParseError.peerHasInvalidPersistentKeepAlive(persistentKeepAliveString) - } - peer.persistentKeepAlive = persistentKeepAlive - } - return peer - } - -} diff --git a/ios/Tunnel/WsLink.swift b/ios/Tunnel/WsLink.swift deleted file mode 100644 index dfdff41..0000000 --- a/ios/Tunnel/WsLink.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation - -/// Channel «Облако»: one binary WebSocket message = one obfuscated packet, both ways. -/// pcarelay (C) calls send() for every packet from the obfuscator; received messages go back via pcarelay_ws_in(). -final class WsLink { - static let shared = WsLink() - - private let q = DispatchQueue(label: "pca.ws") - private let session = URLSession(configuration: .ephemeral) - private var url: URL? - private var task: URLSessionWebSocketTask? - - func start(_ url: URL) { - q.async { self.url = url; self.connect() } - } - - func send(_ p: UnsafePointer?, _ n: Int32) { - guard let p, n > 0 else { return } - let data = Data(bytes: p, count: Int(n)) - q.async { - if self.task == nil { self.connect() } - guard let t = self.task else { return } - t.send(.data(data)) { [weak self] err in - if let err { self?.q.async { if self?.task === t { self?.drop("send: \(err.localizedDescription)") } } } - } - } - } - - private func connect() { - guard let url, task == nil else { return } - let t = session.webSocketTask(with: url) - t.maximumMessageSize = 1 << 20 - task = t - t.resume() - NSLog("PCAPhobos ws: connect %@", url.absoluteString) - receive(t) - } - - private func receive(_ t: URLSessionWebSocketTask) { - t.receive { [weak self] result in - switch result { - case .success(.data(let d)): - d.withUnsafeBytes { pcarelay_ws_in($0.bindMemory(to: UInt8.self).baseAddress, Int32(d.count)) } - self?.receive(t) - case .success: - self?.receive(t) - case .failure(let err): - self?.q.async { if self?.task === t { self?.drop("recv: \(err.localizedDescription)") } } - } - } - } - - /// Next packet from the obfuscator reconnects (WireGuard keepalives make that quick). - private func drop(_ why: String) { - NSLog("PCAPhobos ws: %@", why) - task?.cancel(with: .goingAway, reason: nil) - task = nil - } -} diff --git a/ios/Tunnel/obfuscator/config.c b/ios/Tunnel/obfuscator/config.c deleted file mode 100644 index 75c2477..0000000 --- a/ios/Tunnel/obfuscator/config.c +++ /dev/null @@ -1,535 +0,0 @@ -#include -#include -#include -#include -#include "config.h" -#include "wg-obfuscator.h" -#include "mini_argp.h" -#include "masking.h" - -// Executable name -static const char *arg0; - -/* The options we understand. */ -static const mini_argp_opt options[] = { - { "help", '?', 0 }, - { "config", 'c', 1 }, - { "source-if", 'i', 1 }, - { "source", 's', 1 }, - { "source-lport", 'p', 1 }, - { "target-if", 'o', 1 }, - { "target", 't', 1 }, - { "target-lport", 'r', 1 }, - { "key", 'k', 1 }, - { "masking", 'a', 1 }, - { "static-bindings", 'b', 1 }, - { "max-clients" , 'm', 1 }, - { "idle-timeout", 'l', 1 }, - { "in-timeout", 'n', 1 }, - { "max-dummy", 'd', 1 }, - { "fwmark", 'f', 1 }, - { "allow-clean", 'e', 0 }, - { "verbose", 'v', 1 }, - { "log-file", 'L', 1 }, - { "log-timestamps", 'T', 1 }, - { "resolve-interval", 'R', 1 }, - { 0 } -}; - -static void show_usage(void) -{ - printf("Usage: %s [options]\n%s", arg0, - " -?, --help Give this help list\n" - "\n" - "Main settings:\n" - " -c, --config= Read configuration from file\n" - " (can be used instead of the rest arguments)\n" - " -i, --source-if= Source interface to listen on\n" - " (optional, default - 0.0.0.0, e.g. all)\n" - " -p, --source-lport= Source port to listen\n" - " -t, --target=: Target IP and port\n" - " -k, --key= Obfuscation key \n" - " (required, must be 1-255 characters long)\n" - " -a, --masking= Masking type (optional, default - AUTO)\n" - " Supported values: STUN, AUTO, NONE\n" - " -b, --static-bindings=::,...\n" - " Comma-separated static bindings for two-way mode\n" - " as ::\n" - " You can also repeat this option.\n" - " -f, --fwmark= Firewall mark to set on all packets\n" - " (optional, default - 0, e.g. disabled)\n" - " -v, --verbose= Verbosity level (optional, default - INFO)\n" - " ERRORS (critical errors only)\n" - " WARNINGS (important messages)\n" - " 2 - INFO (informational messages: status messages,\n" - " connection established, etc.)\n" - " 3 - DEBUG (detailed debug messages)\n" - " 4 - TRACE (very detailed debug messages, including\n" - " packet dumps)\n" - " -L, --log-file= Write the log to this file instead of stderr\n" - " (optional, the file is reopened on SIGHUP)\n" - " -T, --log-timestamps= Prefix log lines with a timestamp\n" - " (optional, default - AUTO, e.g. only when\n" - " writing to a log file)\n" - " Supported values: AUTO, TRUE, FALSE\n" - "\n" - "Additional options:\n" - " -m, --max-clients= Maximum number of clients (default: 1024)\n" - " -l, --idle-timeout= Idle timeout in seconds (default: 300)\n" - " -n, --in-timeout= Incoming timeout in seconds (default: 0 - disabled)\n" - " -d, --max-dummy= Maximum length of dummy bytes for data packets\n" - " (default: 4)\n" - " -e, --allow-clean For servers, allow non-obfuscated incoming connections\n" - " -R, --resolve-interval=\n" - " Re-resolve target and static-binding hostnames\n" - " every N seconds (default: 0 - disabled).\n" - " SIGHUP always triggers a refresh.\n" - " If non-zero, a failed resolve at startup is retried\n" - " instead of exiting.\n"); -} - -static int parse_opt(const char *lname, char sname, const char *val, void *ctx); - -/** - * @brief Resets the configuration structure to its default values. - * - * This function clears the configuration structure by setting all fields to zero - * and resetting the verbosity level to the default value. - * - * @param config Pointer to the obfuscator_config structure to be reset. - */ -static void reset_config(obfuscator_config_t *config) -{ - if (config->static_bindings) { - free(config->static_bindings); - } - memset(config, 0, sizeof(*config)); - config->max_clients = MAX_CLIENTS_DEFAULT; - config->idle_timeout = IDLE_TIMEOUT_DEFAULT; - config->in_timeout = IN_TIMEOUT_DEFAULT; - config->max_dummy_length_data = MAX_DUMMY_LENGTH_DATA_DEFAULT; - config->log_timestamps = -1; // auto - verbose = LL_DEFAULT; -} - -/** - * Checks if the given string represents a valid integer. - * - * @param str Pointer to the null-terminated string to check. - * @return Non-zero value if the string is a valid integer, 0 otherwise. - */ -static uint8_t is_integer(const char *str) -{ - if (!str || !*str) { - return 0; // Empty string is not an integer - } - while (*str) { - if (!isdigit((unsigned char)*str)) { - return 0; // Non-digit character found - } - str++; - } - return 1; // All characters are digits -} - -/** - * Parses a boolean value from a string. - * - * @param str Pointer to the null-terminated string to parse. - * @return 1 for true values ("true", "yes", "on", "1"), - * 0 for false values ("false", "no", "off", "0"), - * -1 if the string is not a valid boolean value. - */ -static int parse_bool(const char *str) -{ - char lower[16]; - if (!str || !*str || strlen(str) >= sizeof(lower)) { - return -1; - } - strcpy(lower, str); - for (char *p = lower; *p; ++p) *p = tolower((unsigned char)*p); - if (!strcmp(lower, "true") || !strcmp(lower, "yes") || !strcmp(lower, "on") || !strcmp(lower, "1")) { - return 1; - } - if (!strcmp(lower, "false") || !strcmp(lower, "no") || !strcmp(lower, "off") || !strcmp(lower, "0")) { - return 0; - } - return -1; -} - -/** - * @brief Reads and processes the configuration file. - * - * This function opens the specified configuration file and parses its contents - * to initialize or update the application's configuration settings. - * - * @param filename The path to the configuration file to be read. - * @param config Pointer to the obfuscator_config structure where the parsed settings will be stored. - */ -static void read_config_file(const char *filename, obfuscator_config_t *config) -{ - // Read configuration from the file - uint8_t first_section = 1; // Flag to indicate if this is the first section being processed - char line[10 * 1024]; // Buffer to hold each line from the config file - - FILE *config_file = fopen(filename, "r"); - if (config_file == NULL) { - perror("Can't open config file"); - exit(EXIT_FAILURE); - } - - while (fgets(line, sizeof(line), config_file)) { - // Remove trailing newlines, carriage returns, spaces and tabs - while (strlen(line) && (line[strlen(line) - 1] == '\n' || line[strlen(line) - 1] == '\r' - || line[strlen(line) - 1] == ' ' || line[strlen(line) - 1] == '\t')) { - line[strlen(line) - 1] = 0; - } - // Remove leading spaces and tabs - while (strlen(line) && (line[0] == ' ' || line[0] == '\t')) { - memmove(line, line + 1, strlen(line)); - } - // Ignore comments - char *comment_index = strstr(line, "#"); - if (comment_index != NULL) { - *comment_index = 0; - } - // Skip empty lines or with spaces only - size_t line_len = strlen(line); - if (strspn(line, " \t\r\n") == line_len) { - continue; - } - - // It can be new section - if (line_len > 0 && line[0] == '[' && line[line_len - 1] == ']') { - if (!first_section) { - // new config, need to fork the process - pid_t pid = fork(); - if (pid < 0) { - log(LL_ERROR, "Can't fork a new instance - %s (%d)", strerror(errno), errno); - exit(EXIT_FAILURE); - } - if (pid == 0) { - // Close in the child process - fclose(config_file); - // Stop config file processing for this instance - return; - } - // Remember the instance to be able to forward signals to it - register_child_instance(pid); - } - size_t len = strlen(line) - 2; - if (len > sizeof(section_name) - 1) { - len = sizeof(section_name) - 1; - } - strncpy(section_name, line + 1, len); - section_name[len] = 0; - - // Reset all the parameters - reset_config(config); - - first_section = 0; // We have processed the first section - continue; - } - - // Parse key-value pairs - char *key = strtok(line, "="); - key = trim(key); - while (strlen(key) && (key[strlen(key) - 1] == ' ' || key[strlen(key) - 1] == '\t' || key[strlen(key) - 1] == '\r' || key[strlen(key) - 1] == '\n')) { - key[strlen(key) - 1] = 0; - } - char *value = strtok(NULL, "="); - if (value == NULL) { - log(LL_ERROR, "Invalid configuration line: %s", line); - exit(EXIT_FAILURE); - } - value = trim(value); - if (!*value) { - log(LL_ERROR, "Invalid configuration line: %s", line); - exit(EXIT_FAILURE); - } - const mini_argp_opt *o = margp_find(options, key, 0); - if (o == NULL) { - log(LL_ERROR, "Unknown configuration key: %s", key); - exit(EXIT_FAILURE); - } - if (!o->has_arg) { - // Flag options are represented as booleans in the config file - int b = parse_bool(value); - if (b < 0) { - log(LL_ERROR, "Configuration key '%s' accepts only boolean values (true/yes/on/1 or false/no/off/0)", key); - exit(EXIT_FAILURE); - } - if (!b) { - continue; - } - value = NULL; - } - parse_opt(o->long_name, o->short_name, value, config); - } - fclose(config_file); -} - -/* Parse a single option. */ -static int parse_opt(const char *lname, char sname, const char *val, void *ctx) -{ - obfuscator_config_t *config = (obfuscator_config_t *)ctx; - char val_lower[16]; - - switch (sname) - { - case '?': - // Show usage and exit - show_usage(); - exit(EXIT_SUCCESS); - case 'c': - read_config_file(val, config); - break; - case 'i': - strncpy(config->client_interface, val, sizeof(config->client_interface) - 1); - config->client_interface[sizeof(config->client_interface) - 1] = 0; // Ensure null-termination - config->client_interface_set = 1; - break; - case 'p': - if (!is_integer(val)) { - log(LL_ERROR, "Invalid source port: %s (must be an integer)", val); - exit(EXIT_FAILURE); - } - config->listen_port = atoi(val); - if (config->listen_port <= 0 || config->listen_port > 65535) { - log(LL_ERROR, "Invalid listen port: %s (must be between 1 and 65535)", val); - exit(EXIT_FAILURE); - } - config->listen_port_set = 1; - break; - case 't': - strncpy(config->forward_host_port, val, sizeof(config->forward_host_port) - 1); - config->forward_host_port[sizeof(config->forward_host_port) - 1] = 0; // Ensure null-termination - config->forward_host_port_set = 1; - break; - case 'b': - { - char *new_bindings; - if (!config->static_bindings) { - new_bindings = strdup(val); - } else { - size_t old_len = strlen(config->static_bindings); - new_bindings = realloc(config->static_bindings, old_len + strlen(val) + 2); - if (new_bindings) { - new_bindings[old_len] = ','; - strcpy(new_bindings + old_len + 1, val); - } - } - if (!new_bindings) { - log(LL_ERROR, "Out of memory while parsing static-bindings"); - exit(EXIT_FAILURE); - } - config->static_bindings = new_bindings; - config->static_bindings_set = 1; - } - break; - case 'k': - strncpy(config->xor_key, val, sizeof(config->xor_key)); - config->xor_key[sizeof(config->xor_key) - 1] = 0; // Ensure null-termination - if (strlen(config->xor_key) == 0) { - log(LL_ERROR, "XOR key cannot be empty"); - exit(EXIT_FAILURE); - } - config->xor_key_set = 1; - break; - case 'm': - if (!is_integer(val)) { - log(LL_ERROR, "Invalid maximum number of clients: %s (must be an integer)", val); - exit(EXIT_FAILURE); - } - config->max_clients = atoi(val); - if (config->max_clients <= 0) { - log(LL_ERROR, "Invalid maximum number of clients: %s (must be greater than 0)", val); - exit(EXIT_FAILURE); - } - break; - case 'l': - if (!is_integer(val)) { - log(LL_ERROR, "Invalid idle timeout: %s (must be an integer)", val); - exit(EXIT_FAILURE); - } - config->idle_timeout = atol(val); - if (config->idle_timeout <= 0) { - log(LL_ERROR, "Invalid idle timeout: %s (must be greater than 0)", val); - exit(EXIT_FAILURE); - } - config->idle_timeout *= 1000; // Convert to milliseconds - break; - case 'n': - if (!is_integer(val)) { - log(LL_ERROR, "Invalid incoming timeout: %s (must be an integer)", val); - exit(EXIT_FAILURE); - } - config->in_timeout = atol(val); - if (config->in_timeout <= 0) { - log(LL_ERROR, "Invalid incoming timeout: %s (must be greater than 0)", val); - exit(EXIT_FAILURE); - } - config->in_timeout *= 1000; // Convert to milliseconds - break; - case 'd': - if (!is_integer(val)) { - log(LL_ERROR, "Invalid maximum dummy length for data packets: %s (must be an integer)", val); - exit(EXIT_FAILURE); - } - config->max_dummy_length_data = atoi(val); - if (config->max_dummy_length_data < 0 || config->max_dummy_length_data > MAX_DUMMY_LENGTH_TOTAL) { - log(LL_ERROR, "Invalid maximum dummy length for data packets: %s (must be between 0 and %d)", val, MAX_DUMMY_LENGTH_TOTAL); - exit(EXIT_FAILURE); - } - break; - case 'e': - config->allow_clean = 1; - break; - case 'f': - // parse string with decimal and hexadecimal support -#ifdef __linux__ - { - long int v = strtol(val, NULL, 0); - if (v <= 0 || v > UINT16_MAX) { - log(LL_ERROR, "Invalid firewall mark: %s", val); - exit(EXIT_FAILURE); - } - config->fwmark = (uint16_t)v; - } -#else - log(LL_WARN, "Firewall mark is not supported on this platform"); -#endif - break; - case 'a': - { - strncpy(val_lower, val, sizeof(val_lower) - 1); - val_lower[sizeof(val_lower) - 1] = 0; - for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p); - if (strcmp(val_lower, "none") == 0) { - config->masking_handler = NULL; - config->masking_handler_set = 1; - break; - } - if (strcmp(val_lower, "auto") == 0) { - config->masking_handler = NULL; - config->masking_handler_set = 0; - break; - } - masking_handler_t *handler = get_masking_handler_by_name(val_lower); - if (handler == NULL) { - log(LL_ERROR, "Unknown masking type: %s", val); - exit(EXIT_FAILURE); - } - config->masking_handler = handler; - config->masking_handler_set = 1; - } - break; - case 'v': - strncpy(val_lower, val, sizeof(val_lower) - 1); - val_lower[sizeof(val_lower) - 1] = 0; - for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p); - if (strcmp(val_lower, "error") == 0) { - verbose = LL_ERROR; - } else if (strcmp(val_lower, "warn") == 0) { - verbose = LL_WARN; - } else if (strcmp(val_lower, "info") == 0) { - verbose = LL_INFO; - } else if (strcmp(val_lower, "debug") == 0) { - verbose = LL_DEBUG; - } else if (strcmp(val_lower, "trace") == 0) { - verbose = LL_TRACE; - } else { - // check if it's a number - if (is_integer(val)) { - verbose = atoi(val); - if (verbose < 0 || verbose > 4) { - log(LL_ERROR, "Invalid verbosity level: %s (must be one of 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')", val); - exit(EXIT_FAILURE); - } - } else { - log(LL_ERROR, "Invalid verbosity level: %s (must be one of 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')", val); - exit(EXIT_FAILURE); - } - } - break; - case 'L': - strncpy(config->log_file, val, sizeof(config->log_file) - 1); - config->log_file[sizeof(config->log_file) - 1] = 0; // Ensure null-termination - if (strlen(config->log_file) == 0) { - log(LL_ERROR, "Log file path cannot be empty"); - exit(EXIT_FAILURE); - } - config->log_file_set = 1; - break; - case 'T': - { - strncpy(val_lower, val, sizeof(val_lower) - 1); - val_lower[sizeof(val_lower) - 1] = 0; - for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p); - if (strcmp(val_lower, "auto") == 0) { - config->log_timestamps = -1; - break; - } - int b = parse_bool(val_lower); - if (b < 0) { - log(LL_ERROR, "Invalid log timestamps mode: %s (must be one of 'AUTO', 'TRUE', 'FALSE')", val); - exit(EXIT_FAILURE); - } - config->log_timestamps = b; - } - break; - case 'R': - if (!is_integer(val)) { - log(LL_ERROR, "Invalid resolve interval: %s (must be an integer)", val); - exit(EXIT_FAILURE); - } - config->resolve_interval = atol(val); - if (config->resolve_interval < 0) { - log(LL_ERROR, "Invalid resolve interval: %s (must be 0 or greater)", val); - exit(EXIT_FAILURE); - } - config->resolve_interval *= 1000; // Convert to milliseconds - break; - default: - // should never happen - return -1; - } - return 0; -} - -int parse_config(int argc, char **argv, obfuscator_config_t *config) -{ - /* Parse command line arguments */ - reset_config(config); - arg0 = argv[0]; // Save the executable name - if (argc == 1) { - fprintf(stderr, "No arguments provided, use \"%s --help\" command for usage information\n", argv[0]); - return -1; - } - if (mini_argp_parse(argc, argv, options, config, parse_opt) != 0) { - fprintf(stderr, "Failed to parse command line arguments\n"); - return -1; - } - - return 0; -} - -/** - * @brief Removes leading and trailing whitespace characters from the input string. - * - * This function modifies the input string in place by trimming any whitespace - * characters (such as spaces, tabs, or newlines) from both the beginning and end. - * - * @param s Pointer to the null-terminated string to be trimmed. - * @return Pointer to the trimmed string. - */ -char *trim(char *s) { - char *end; - // Trim leading spaces, tabs, carriage returns and newlines - while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')) s++; - if (!*s) return s; - // Trim trailing spaces, tabs, carriage returns and newlines - end = s + strlen(s) - 1; - while (end > s && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = 0; - return s; -} diff --git a/ios/Tunnel/obfuscator/config.h b/ios/Tunnel/obfuscator/config.h deleted file mode 100644 index 1083bc5..0000000 --- a/ios/Tunnel/obfuscator/config.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _CONFIG_H_ -#define _CONFIG_H_ - -#include "wg-obfuscator.h" - -char *trim(char *s); -int parse_config(int argc, char **argv, obfuscator_config_t *config); - -#endif // _CONFIG_H_ diff --git a/ios/Tunnel/obfuscator/logging.c b/ios/Tunnel/obfuscator/logging.c deleted file mode 100644 index 3b809f1..0000000 --- a/ios/Tunnel/obfuscator/logging.c +++ /dev/null @@ -1,258 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "wg-obfuscator.h" - -#ifndef O_BINARY -#define O_BINARY 0 -#endif - -#ifndef STDERR_FILENO -#define STDERR_FILENO 2 -#endif - -// Log files may contain client addresses, so they are not world-readable -#define LOG_FILE_MODE 0640 - -// File descriptor of the log file, -1 when logging to stderr -static int log_fd = -1; -// Path of the log file, kept to be able to reopen it on SIGHUP -static char *log_path = NULL; -// 1 if every line must be prefixed with a timestamp -static int log_timestamps = 0; - -/** - * @brief Returns the single character representing a logging level. - */ -static char level_char(int level) -{ - switch (level) { - case LL_ERROR: return 'E'; - case LL_WARN: return 'W'; - case LL_INFO: return 'I'; - case LL_DEBUG: return 'D'; - case LL_TRACE: return 'T'; - default: return '?'; - } -} - -/** - * @brief Writes the whole buffer to the descriptor, retrying on partial writes. - * - * @param fd Descriptor to write to. - * @param buf Data to write. - * @param len Number of bytes to write. - */ -static void write_all(int fd, const char *buf, size_t len) -{ - while (len) { - ssize_t written = write(fd, buf, len); - if (written <= 0) { - if (written < 0 && errno == EINTR) { - continue; - } - // Nothing sensible can be done about a broken log, drop the rest of the line - return; - } - buf += written; - len -= (size_t)written; - } -} - -/** - * @brief Appends formatted text to the buffer without ever overflowing it. - * - * @param buf Buffer to append to. - * @param size Usable size of the buffer, including the terminating zero. - * @param len Current length of the text in the buffer. - * @param fmt Format string. - * @param args Arguments for the format string. - * @return New length of the text in the buffer. - */ -static size_t append_vfmt(char *buf, size_t size, size_t len, const char *fmt, va_list args) -{ - int n; - - if (len + 1 >= size) { - return len; - } - n = vsnprintf(buf + len, size - len, fmt, args); - if (n < 0) { - return len; - } - if ((size_t)n >= size - len) { - return size - 1; // truncated - } - return len + (size_t)n; -} - -/** - * @brief Appends formatted text to the buffer without ever overflowing it. - * - * @param buf Buffer to append to. - * @param size Usable size of the buffer, including the terminating zero. - * @param len Current length of the text in the buffer. - * @param fmt Format string. - * @return New length of the text in the buffer. - */ -static size_t append_fmt(char *buf, size_t size, size_t len, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - len = append_vfmt(buf, size, len, fmt, args); - va_end(args); - - return len; -} - -/** - * @brief Writes the common line prefix (timestamp and instance name) to the buffer. - * - * @param buf Buffer to write to. - * @param size Usable size of the buffer, including the terminating zero. - * @param level Logging level of the line. - * @return Length of the prefix. - */ -static size_t log_prefix(char *buf, size_t size, int level) -{ - size_t len = 0; - - if (log_timestamps) { - struct timespec ts; - struct tm *tm; - if (clock_gettime(CLOCK_REALTIME, &ts) == 0 && (tm = localtime(&ts.tv_sec)) != NULL) { - len = strftime(buf, size, "%Y-%m-%d %H:%M:%S", tm); - len = append_fmt(buf, size, len, ".%03ld ", (long)(ts.tv_nsec / 1000000)); - } - } - - return append_fmt(buf, size, len, "[%s][%c] ", section_name, level_char(level)); -} - -/** - * @brief Terminates the line and writes it out with a single write() call. - * - * A single write() to a descriptor opened with O_APPEND keeps lines of different - * instances from interleaving when they share the same log file. - * - * @param buf Buffer holding the line, must have one spare byte for the newline. - * @param len Length of the line. - */ -static void log_emit(char *buf, size_t len) -{ - buf[len++] = '\n'; - write_all(log_fd >= 0 ? log_fd : STDERR_FILENO, buf, len); -} - -/** - * @brief Writes a single log line. - * - * This is the implementation behind the log() macro, which is the one that - * checks the verbosity level, so the line written here is unconditional. - * - * @param level Logging level of the line. - * @param fmt Format string. - */ -void log_printf(int level, const char *fmt, ...) -{ - char buf[LOG_LINE_MAX]; - va_list args; - size_t len = log_prefix(buf, sizeof(buf) - 1, level); - - va_start(args, fmt); - len = append_vfmt(buf, sizeof(buf) - 1, len, fmt, args); - va_end(args); - - log_emit(buf, len); -} - -/** - * @brief Writes a hexadecimal dump of a buffer, one or more prefixed lines. - * - * @param level Logging level of the dump. - * @param prefix Text to put right after the line prefix, e.g. a direction marker. - * @param data Data to dump. - * @param length Number of bytes to dump. - */ -void log_hexdump(int level, const char *prefix, const uint8_t *data, int length) -{ - char buf[LOG_LINE_MAX]; - int i = 0; - - if (verbose < level) { - return; - } - - // Long dumps are split into several lines, each one with its own prefix - do { - size_t len = log_prefix(buf, sizeof(buf) - 1, level); - len = append_fmt(buf, sizeof(buf) - 1, len, "%s", prefix); - while ((i < length) && (len + 3 < sizeof(buf) - 1)) { - len = append_fmt(buf, sizeof(buf) - 1, len, "%02X ", data[i++]); - } - log_emit(buf, len); - } while (i < length); -} - -/** - * @brief Initializes logging. - * - * @param path Path of the log file or NULL to keep logging to stderr. - * @param timestamps_mode 1 to force timestamps on, 0 to force them off, - * negative to enable them only when logging to a file. - */ -void log_init(const char *path, int8_t timestamps_mode) -{ - log_timestamps = (timestamps_mode >= 0) ? (timestamps_mode != 0) : (path != NULL); - - if (!path) { - return; - } - - log_path = strdup(path); - if (!log_path) { - fprintf(stderr, "Out of memory while initializing the log file\n"); - exit(EXIT_FAILURE); - } - - log_fd = open(path, O_WRONLY | O_CREAT | O_APPEND | O_BINARY, LOG_FILE_MODE); - if (log_fd < 0) { - // Logging into nowhere is worse than not starting at all - fprintf(stderr, "Can't open log file '%s' - %s (%d)\n", path, strerror(errno), errno); - exit(EXIT_FAILURE); - } - - // Make every log file start with the version, no matter the verbosity level - log_printf(LL_INFO, "Starting %s", version_string()); -} - -/** - * @brief Reopens the log file, to be called after the log has been rotated. - */ -void log_reopen(void) -{ - int fd; - - if (!log_path) { - return; - } - - fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_BINARY, LOG_FILE_MODE); - if (fd < 0) { - // Keep the old descriptor, losing the log completely would be worse - fprintf(stderr, "Can't reopen log file '%s' - %s (%d)\n", log_path, strerror(errno), errno); - return; - } - if (log_fd >= 0) { - close(log_fd); - } - log_fd = fd; - - log_printf(LL_INFO, "Log file reopened, %s", version_string()); -} diff --git a/ios/Tunnel/obfuscator/masking.c b/ios/Tunnel/obfuscator/masking.c deleted file mode 100644 index bc9b4e0..0000000 --- a/ios/Tunnel/obfuscator/masking.c +++ /dev/null @@ -1,170 +0,0 @@ -#include -#include -#include -#include "wg-obfuscator.h" -#include "masking.h" -#include "masking_handlers.h" - -static _Thread_local struct { - int listen_sock; - struct sockaddr_in *sender_addr; - int server_sock; -} g_send_ctx; - -static ssize_t send_to_client_cb(uint8_t *buffer, int length) { - return sendto(g_send_ctx.listen_sock, buffer, length, 0, (struct sockaddr *)g_send_ctx.sender_addr, sizeof(*g_send_ctx.sender_addr)); -} - -static ssize_t send_to_server_cb(uint8_t *buffer, int length) { - return send(g_send_ctx.server_sock, buffer, length, 0); -} - -masking_handler_t * get_masking_handler_by_name(const char *name) { - if (!name) return NULL; - - for (int i = 0; masking_handlers[i]; ++i) { - char handler_name_lower[MASKING_HANDLER_NAME_LEN]; - strncpy(handler_name_lower, masking_handlers[i]->name, sizeof(handler_name_lower) - 1); - handler_name_lower[sizeof(handler_name_lower) - 1] = 0; - for (char *p = handler_name_lower; *p; ++p) *p = tolower((unsigned char)*p); - - if (strcmp(name, handler_name_lower) == 0) { - return masking_handlers[i]; - } - } - return NULL; -} - -void masking_on_handshake_req_from_client(obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *client_addr, - struct sockaddr_in *server_addr) { - if (!client->masking_handler || !client->masking_handler->on_handshake_req) { - return; - } - - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = &client->client_addr; - g_send_ctx.server_sock = client->server_sock; - client->masking_handler->on_handshake_req(config, client, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, send_to_server_cb); -} - -void masking_on_handshake_req_from_server(obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *client_addr, - struct sockaddr_in *server_addr) { - if (!client->masking_handler || !client->masking_handler->on_handshake_req) { - return; - } - - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = &client->client_addr; - g_send_ctx.server_sock = client->server_sock; - client->masking_handler->on_handshake_req(config, client, DIR_SERVER_TO_CLIENT, server_addr, client_addr, send_to_server_cb, send_to_client_cb); -} - -int masking_unwrap_from_client(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, // can be NULL! - int listen_sock, - struct sockaddr_in *client_addr, - struct sockaddr_in *server_addr, - masking_handler_t **masking_handler_out) { - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = client_addr; - g_send_ctx.server_sock = client ? client->server_sock : 0; - - if (!client && !config->masking_handler_set) { - // Brueteforce detection of masking type if no client entry and no default masking handler - for (int i = 0; masking_handlers[i]; ++i) { - // A handler may move *buffer_ptr while unwrapping. If it does not match - // (returns < 0), restore the pointer so the next handler sees the original data. - uint8_t *saved_buffer = *buffer_ptr; - int r = masking_handlers[i]->on_data_unwrap(buffer_ptr, length, config, NULL, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, send_to_server_cb); - if (r < 0) { - *buffer_ptr = saved_buffer; - continue; - } - // Found a matching masking handler - log(LL_TRACE, "Autodetected masking handler for packet from %s:%d: %s", inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), masking_handlers[i]->name); - if (masking_handler_out) { - *masking_handler_out = masking_handlers[i]; - } - return r; - } - - // No matching masking handler found - if (masking_handler_out) { - *masking_handler_out = NULL; - } - return length; - } - - masking_handler_t *handler = client ? client->masking_handler : config->masking_handler; - if (!handler || !handler->on_data_unwrap) { - return length; // no masking handler, nothing to do - } - - return handler->on_data_unwrap(buffer_ptr, length, config, client, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, client ? send_to_server_cb : NULL); -} - -int masking_unwrap_from_server(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr) { - if (!client->masking_handler || !client->masking_handler->on_data_unwrap) { - return length; // no masking handler, nothing to do - } - - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = &client->client_addr; - g_send_ctx.server_sock = client->server_sock; - return client->masking_handler->on_data_unwrap(buffer_ptr, length, config, client, DIR_SERVER_TO_CLIENT, server_addr, &client->client_addr, send_to_server_cb, send_to_client_cb); -} - -int masking_data_wrap_to_client(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr) { - if (!client->masking_handler || !client->masking_handler->on_data_wrap) { - return length; // no masking handler, nothing to do - } - - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = &client->client_addr; - g_send_ctx.server_sock = client->server_sock; - return client->masking_handler->on_data_wrap(buffer_ptr, length, config, client, DIR_SERVER_TO_CLIENT, server_addr, &client->client_addr, send_to_server_cb, send_to_client_cb); -} - -int masking_data_wrap_to_server(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr) { - if (!client->masking_handler || !client->masking_handler->on_data_wrap) { - return length; // no masking handler, nothing to do - } - - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = &client->client_addr; - g_send_ctx.server_sock = client->server_sock; - return client->masking_handler->on_data_wrap(buffer_ptr, length, config, client, DIR_CLIENT_TO_SERVER, &client->client_addr, server_addr, send_to_client_cb, send_to_server_cb); -} - -void masking_on_timer(obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr) { - if (!client->masking_handler || !client->masking_handler->on_timer) { - return; - } - - g_send_ctx.listen_sock = listen_sock; - g_send_ctx.sender_addr = &client->client_addr; - g_send_ctx.server_sock = client->server_sock; - client->masking_handler->on_timer(config, client, &client->client_addr, server_addr, send_to_client_cb, send_to_server_cb); -} diff --git a/ios/Tunnel/obfuscator/masking.h b/ios/Tunnel/obfuscator/masking.h deleted file mode 100644 index 4a9cc22..0000000 --- a/ios/Tunnel/obfuscator/masking.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef _MASKING_H_ -#define _MASKING_H_ - -#include -#include -#include "wg-obfuscator.h" - -#define MASKING_HANDLER_NAME_LEN 32 - -typedef ssize_t (*send_data_callback_t)(uint8_t *buffer, int length); - -typedef void (*masking_event_handler_t)(obfuscator_config_t *config, - client_entry_t *client, - direction_t direction, - const struct sockaddr_in *src_addr, - const struct sockaddr_in *dest_addr, - send_data_callback_t send_back_callback, - send_data_callback_t send_forward_callback); - -typedef int (*masking_data_handler_t)(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - direction_t direction, - const struct sockaddr_in *src_addr, - const struct sockaddr_in *dest_addr, - send_data_callback_t send_back_callback, - send_data_callback_t send_forward_callback); - -typedef void (*masking_timer_handler_t)(obfuscator_config_t *config, - client_entry_t *client, - const struct sockaddr_in *client_addr, - const struct sockaddr_in *server_addr, - send_data_callback_t send_to_client_callback, - send_data_callback_t send_to_server_callback); - -struct masking_handler { - char name[MASKING_HANDLER_NAME_LEN]; - masking_event_handler_t on_handshake_req; - masking_data_handler_t on_data_wrap; - masking_data_handler_t on_data_unwrap; - masking_timer_handler_t on_timer; - uint32_t timer_interval_s; -}; -typedef struct masking_handler masking_handler_t; - -masking_handler_t * get_masking_handler_by_name(const char *name); - -void masking_on_handshake_req_from_client(obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *client_addr, - struct sockaddr_in *server_addr); - -void masking_on_handshake_req_from_server(obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *client_addr, - struct sockaddr_in *server_addr); - -int masking_data_wrap_to_client(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr); - -int masking_data_wrap_to_server(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr); - -int masking_unwrap_from_client(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *client_addr, - struct sockaddr_in *server_addr, - masking_handler_t **masking_handler_out); - -int masking_unwrap_from_server(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr); - -void masking_on_timer(obfuscator_config_t *config, - client_entry_t *client, - int listen_sock, - struct sockaddr_in *server_addr); - -#endif // _MASKING_H_ diff --git a/ios/Tunnel/obfuscator/masking_handlers.h b/ios/Tunnel/obfuscator/masking_handlers.h deleted file mode 100644 index c7745a7..0000000 --- a/ios/Tunnel/obfuscator/masking_handlers.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _MASKING_HANDLERS_H_ -#define _MASKING_HANDLERS_H_ - -#include "masking.h" - -/* List of available masking handlers */ -#include "masking_stun.h" - -static masking_handler_t * const masking_handlers[] = { - &stun_masking_handler, - NULL -}; - -#endif \ No newline at end of file diff --git a/ios/Tunnel/obfuscator/masking_stun.c b/ios/Tunnel/obfuscator/masking_stun.c deleted file mode 100644 index 0a274b9..0000000 --- a/ios/Tunnel/obfuscator/masking_stun.c +++ /dev/null @@ -1,298 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "wg-obfuscator.h" -#include "masking.h" -#include "masking_stun.h" - -// Bytes prepended in front of the payload when wrapping data in a STUN Data Indication -// (20-byte STUN header + 4-byte attribute header). Must fit into PREBUFFER_SIZE so that -// wrapping can be done by shifting the buffer pointer instead of moving the payload. -#define STUN_WRAP_OVERHEAD 24 -_Static_assert(STUN_WRAP_OVERHEAD <= PREBUFFER_SIZE, - "PREBUFFER_SIZE is too small to hold the STUN wrap header"); - -static void rand_bytes(uint8_t* p, size_t n){ for(size_t i=0;i> 8; - b[1] = type & 0xFF; - b[2] = mlen >> 8; - b[3] = mlen & 0xFF; - memcpy(b + 4, COOKIE_BE, 4); - memcpy(b + 8, txid, 12); - return 20; -} - -static int stun_attr_xor_mapped_addr(uint8_t *b, const struct sockaddr_in *src) { - // type,len - b[0] = STUN_ATTR_XORMAPPED >> 8; - b[1] = STUN_ATTR_XORMAPPED & 0xFF; - b[2] = 0; - b[3] = 8; // len=8 (family + port + addr) - b[4] = 0; - b[5] = 0x01; // family IPv4 - memcpy(b + 6, &src->sin_port, 2); // network-order bytes - ((uint8_t*)b)[6] ^= COOKIE_BE[0]; - ((uint8_t*)b)[7] ^= COOKIE_BE[1]; - memcpy(b + 8, &src->sin_addr.s_addr, 4); // network-order bytes - ((uint8_t*)b)[8] ^= COOKIE_BE[0]; - ((uint8_t*)b)[9] ^= COOKIE_BE[1]; - ((uint8_t*)b)[10] ^= COOKIE_BE[2]; - ((uint8_t*)b)[11] ^= COOKIE_BE[3]; - return 12; // 4 hdr + 8 val -} - -/* -static int stun_attr_software(uint8_t *b, const char *s) { - uint16_t n = (uint16_t)strlen(s); - uint16_t pad = (4 - (n & 3)) & 3; - if (b) { - b[0] = STUN_ATTR_SOFTWARE >> 8; - b[1] = STUN_ATTR_SOFTWARE&0xFF; - b[2] = n>>8; - b[3] = n&0xFF; - memcpy(b+4,s,n); - if (pad) memset(b+4+n,0,pad); - } - return 4 + n + pad; -} -*/ - -static uint32_t crc32(const uint8_t *p, size_t n) { - uint32_t crc = ~0u; - for(size_t i = 0; i < n; i++){ - crc ^= p[i]; - for(int k = 0; k < 8; k++) - crc = (crc >> 1) ^ (0xEDB88320u & (-(int)(crc & 1))); - } - return ~crc; -} - -static int stun_attr_fingerprint(uint8_t *pkt, size_t cur_len) { - uint8_t *b = pkt + cur_len; - b[0] = STUN_ATTR_FINGERPR >> 8; - b[1] = STUN_ATTR_FINGERPR & 0xFF; - b[2] = 0; - b[3] = 4; - uint32_t fp = htonl(crc32(pkt, cur_len) ^ 0x5354554eu); - memcpy(b + 4, &fp, 4); - return 8; // 4 hdr + 4 val -} - -static int stun_build_binding_request(uint8_t *out) { - uint8_t txid[12]; - rand_bytes(txid, 12); - stun_write_header(out, STUN_BINDING_REQ, 0, txid); - size_t mlen = 0; - // optional SOFTWARE - //mlen += stun_attr_software(out+20+mlen, "wgo/1.0"); - mlen += stun_attr_fingerprint(out, 20 + mlen); - out[2] = (mlen >> 8) & 0xFF; - out[3] = mlen & 0xFF; - return (int)(20 + mlen); -} - -static int stun_build_binding_success(uint8_t *out, - const uint8_t txid[12], - const struct sockaddr_in *src) { - if (BUFFER_SIZE < 20 + 12 + 8) return -1; // header + XOR-MAPPED-ADDRESS + fingerprint - stun_write_header(out, STUN_BINDING_RESP, 0, (uint8_t*)txid); - size_t mlen = 0; - mlen += stun_attr_xor_mapped_addr(out + 20 + mlen, src); - // optional SOFTWARE - //mlen += stun_attr_software(out+20+mlen, "wgo/1.0"); - mlen += stun_attr_fingerprint(out, 20 + mlen); - out[2] = (mlen >> 8) & 0xFF; - out[3] = mlen & 0xFF; - return (int)(20 + mlen); -} - -static int stun_wrap(uint8_t **buf_ptr, size_t data_len) { - size_t mlen = 0; - - // The caller guarantees the payload already fits into the main buffer (it was - // received/encoded within BUFFER_SIZE), so the wrapped packet cannot overflow - // the tail of full_buffer. The attribute length field is also 16-bit only. - // Note: BUFFER_SIZE (65535) equals the maximum value of a 16-bit unsigned - // integer (0xFFFF), so this single check covers both the buffer limit and - // the STUN attribute length field limit. - if (data_len > BUFFER_SIZE) { - log(LL_WARN, "Can't wrap data in STUN, data too large (%zu bytes)", data_len); - return -ENOMEM; - } - - // Header is prepended into the prebuffer (checked at compile time via _Static_assert). - *buf_ptr -= STUN_WRAP_OVERHEAD; - uint8_t *buf = *buf_ptr; - - uint8_t txid[12]; - rand_bytes(txid,12); - mlen += stun_write_header(buf, STUN_TYPE_DATA_IND, 0, txid); - - buf[mlen] = STUN_ATTR_DATA >> 8; - buf[mlen + 1] = STUN_ATTR_DATA & 0xFF; - buf[mlen + 2] = data_len >> 8; - buf[mlen + 3] = data_len & 0xFF; - - return STUN_WRAP_OVERHEAD + data_len; -} - -static int stun_unwrap(uint8_t **buf_ptr, size_t len) { - uint8_t *buf = *buf_ptr; - if (len < 20 + 4) return -1; // header + attr - - uint16_t msg_type = (buf[0] << 8) | buf[1]; - if (msg_type != STUN_TYPE_DATA_IND) return -1; - - uint16_t msg_len = (buf[2] << 8) | buf[3]; - if (msg_len + 20 > len) return -1; - - uint16_t attr_type = (buf[20] << 8) | buf[21]; - if (attr_type != STUN_ATTR_DATA) return -1; - - uint16_t data_len = (buf[22] << 8) | buf[23]; - if (data_len + 24 > len) return -1; - - // Move the buffer pointer to avoid moving the data - *buf_ptr += 24; - - return data_len; -} - -static void stun_on_handshake_req(obfuscator_config_t *config, - client_entry_t *client, - direction_t direction, - const struct sockaddr_in *src_addr, - const struct sockaddr_in *dest_addr, - send_data_callback_t send_back_callback, - send_data_callback_t send_forward_callback) { - uint8_t buffer[128]; - int len = stun_build_binding_request(buffer); - if (len < 0) return; - - int sent = send_forward_callback(buffer, len); - if (sent < 0) { - serror("Can't send STUN binding request to %s:%d", inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port)); - } else if (sent != len) { - log(LL_WARN, "Partial send of STUN binding request to %s:%d (%d of %d bytes)", inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port), sent, len); - } else { - log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port)); - } -} - -static int stun_on_data_unwrap(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - direction_t direction, - const struct sockaddr_in *src_addr, - const struct sockaddr_in *dest_addr, - send_data_callback_t send_back_callback, - send_data_callback_t send_forward_callback) { - if (!stun_check_magic(*buffer_ptr, length)) { - return -EINVAL; - } - - uint16_t stun_type = stun_peek_type(*buffer_ptr); - - switch (stun_type) { - case STUN_BINDING_REQ: { - // Received STUN Binding Request from client, send Binding Success Response - log(LL_TRACE, "Received STUN Binding Request from %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); - uint8_t txid[12]; - memcpy(txid, (*buffer_ptr) + 8, 12); - int resp_len = stun_build_binding_success(*buffer_ptr, txid, src_addr); - if (resp_len > 0) { - int sent = send_back_callback(*buffer_ptr, resp_len); - if (sent < 0) { - serror("sendto STUN response to %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); - } else if (sent != resp_len) { - log(LL_WARN, "Partial send of STUN Binding Success Response to %s:%d (%d of %d bytes)", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port), sent, resp_len); - } else { - log(LL_TRACE, "Sent STUN Binding Success Response (%d bytes) to %s:%d", resp_len, inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); - } - } else { - log(LL_ERROR, "Failed to build STUN Binding Success Response"); - } - return 0; - } - case STUN_BINDING_RESP: - log(LL_TRACE, "Received STUN Binding Success Response from %s:%d, ignoring", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); - return 0; - case STUN_TYPE_DATA_IND: - length = stun_unwrap(buffer_ptr, length); - if (length < 0) { - log(LL_DEBUG, "Failed to unwrap STUN Data Indication from %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); - return length; - } - log(LL_TRACE, "Unwrapped STUN Data Indication from %s:%d (%d bytes)", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port), length); - return length; - default: - log(LL_DEBUG, "Received unknown STUN type %04X from %s:%d, ignoring", stun_type, inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port)); - return 0; - } -} - -static int stun_on_data_wrap(uint8_t **buffer_ptr, int length, - obfuscator_config_t *config, - client_entry_t *client, - direction_t direction, - const struct sockaddr_in *src_addr, - const struct sockaddr_in *dest_addr, - send_data_callback_t send_back_callback, - send_data_callback_t send_forward_callback) { - return stun_wrap(buffer_ptr, length); -} - -static void stun_on_timer(obfuscator_config_t *config, - client_entry_t *client, - const struct sockaddr_in *client_addr, - const struct sockaddr_in *server_addr, - send_data_callback_t send_to_client_callback, - send_data_callback_t send_to_server_callback) { - uint8_t buffer[128]; - int len = stun_build_binding_request(buffer); - if (len < 0) return; - - if (client->client_obfuscated) { - int sent = send_to_client_callback(buffer, len); - if (sent < 0) { - serror("STUN binding request to client"); - } else if (sent != len) { - log(LL_WARN, "Partial send of STUN binding request to client (%d of %d bytes)", sent, len); - } else { - log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); - } - } - if (client->server_obfuscated) { - int sent = send_to_server_callback(buffer, len); - if (sent < 0) { - serror("STUN binding request to server"); - } else if (sent != len) { - log(LL_WARN, "Partial send of STUN binding request to server (%d of %d bytes)", sent, len); - } else { - log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(server_addr->sin_addr), ntohs(server_addr->sin_port)); - } - } -} - -masking_handler_t stun_masking_handler = { - .name = "STUN", - .on_handshake_req = stun_on_handshake_req, - .on_data_wrap = stun_on_data_wrap, - .on_data_unwrap = stun_on_data_unwrap, - .on_timer = stun_on_timer, - .timer_interval_s = 10, // 10 seconds -}; diff --git a/ios/Tunnel/obfuscator/masking_stun.h b/ios/Tunnel/obfuscator/masking_stun.h deleted file mode 100644 index e6d906a..0000000 --- a/ios/Tunnel/obfuscator/masking_stun.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef _MASKING_STUN_H_ -#define _MASKING_STUN_H_ - -#include -#include -#include "wg-obfuscator.h" - -static const uint8_t COOKIE_BE[4] = {0x21,0x12,0xA4,0x42}; -#define STUN_TYPE_DATA_IND 0x0115 -#define STUN_BINDING_REQ 0x0001 -#define STUN_BINDING_RESP 0x0101 -#define STUN_ATTR_XORMAPPED 0x0020 -#define STUN_ATTR_SOFTWARE 0x8022 -#define STUN_ATTR_FINGERPR 0x8028 -#define STUN_ATTR_DATA 0x0013 - -extern masking_handler_t stun_masking_handler; - -#endif // _MASKING_STUN_H_ diff --git a/ios/Tunnel/obfuscator/mini_argp.h b/ios/Tunnel/obfuscator/mini_argp.h deleted file mode 100644 index 017c348..0000000 --- a/ios/Tunnel/obfuscator/mini_argp.h +++ /dev/null @@ -1,82 +0,0 @@ -/* mini_argp.h - * Tiny callback-based CLI parser */ - -#ifndef _MINI_ARGP_H_ -#define _MINI_ARGP_H_ -#include -#include - -typedef int (*mini_argp_cb)(const char *lname, char sname, - const char *val, void *ctx); - -typedef struct { - const char *long_name; /* "--target" or NULL */ - char short_name; /* 't' or 0 */ - int has_arg; /* 0 flag, 1 needs val */ - mini_argp_cb cb; /* your handler */ -} mini_argp_opt; - -/* internal: find desc by name */ -static const mini_argp_opt * -margp_find(const mini_argp_opt *o, char *lname, char sname) -{ - for (; o->long_name || o->short_name; o++) - if ((lname && o->long_name && !strcmp(lname,o->long_name)) || - (sname && o->short_name==sname)) - return o; - return NULL; -} - -static int mini_argp_parse(int argc, char **argv, - const mini_argp_opt *opts, - void *ctx, - mini_argp_cb cb) -{ - for (int i = 1; i < argc; ++i) { - char *arg = argv[i]; - - /* long option: --foo or --foo=bar */ - if (!strncmp(arg, "--", 2)) { - char *name = arg + 2, *val = NULL; - if ((val = strchr(name, '='))) { *val = 0; ++val; } - - const mini_argp_opt *o = margp_find(opts, name, 0); - if (!o) { fprintf(stderr,"unknown --%s\n", name); return -1; } - if (o->has_arg && !val) { - if (++i == argc) { fprintf(stderr,"--%s needs value\n",name);return -1; } - val = argv[i]; - } - if (!o->has_arg) val = NULL; - mini_argp_cb to_call = o->cb ? o->cb : cb; - if (to_call(o->long_name, o->short_name, val, ctx)) return -1; - } - - /* short(s): -a, -fVAL, -xzvf */ - else if (arg[0]=='-' && arg[1]) { - for (size_t pos=1; arg[pos]; ++pos) { - char c = arg[pos]; - const mini_argp_opt *o = margp_find(opts,NULL,c); - if (!o){ fprintf(stderr,"unknown -%c\n",c); return -1;} - const char *val = NULL; - - if (o->has_arg) { - if (arg[pos+1]) val = &arg[pos+1]; /* -fVAL */ - else if (++i < argc) val = argv[i]; /* -f VAL */ - else { fprintf(stderr,"-%c needs value\n",c); return -1;} - /* Stop further char-scanning: the rest of the arg is the value */ - pos = strlen(arg)-1; - } - mini_argp_cb to_call = o->cb ? o->cb : cb; - if (to_call(o->long_name, o->short_name, val, ctx)) return -1; - } - } - - /* positional not allowed */ - else { - fprintf(stderr,"unexpected arg: %s\n",arg); return -1; - } - } - return 0; -} - -#endif/* _MINI_ARGP_H_ */ diff --git a/ios/Tunnel/obfuscator/obfuscation.c b/ios/Tunnel/obfuscator/obfuscation.c deleted file mode 100644 index 20b4aa4..0000000 --- a/ios/Tunnel/obfuscator/obfuscation.c +++ /dev/null @@ -1,130 +0,0 @@ -#include -#include -#include -#include "wg-obfuscator.h" -#include "obfuscation.h" - -static uint8_t crc_a[256]; // step(crc, 0): contribution of the current CRC state -static uint8_t crc_b[256]; // step(0, inbyte): contribution of the input byte -static uint8_t tables_ready = 0; - -typedef struct { - uint8_t *data; // cached keystream bytes, NULL until first allocation - int valid; // number of valid bytes in data - int cap; // allocated capacity of data - uint8_t crc; // CRC state right after the 'valid' bytes -} keystream_row_t; - -static keystream_row_t rows[256]; - -// Single step of the original bit-by-bit CRC8 update, kept only to build the tables. -static uint8_t crc8_step(uint8_t crc, uint8_t inbyte) { - for (uint8_t j = 0; j < 8; j++) { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) { - crc ^= 0x8C; - } - inbyte >>= 1; - } - return crc; -} - -static void ensure_tables(void) { - if (tables_ready) { - return; - } - for (int i = 0; i < 256; i++) { - crc_a[i] = crc8_step((uint8_t)i, 0); - crc_b[i] = crc8_step(0, (uint8_t)i); - } - tables_ready = 1; -} - -// Ensure the given row holds at least 'need' valid keystream bytes (capped at -// KEYSTREAM_ROW_MAX). On allocation failure the row is left as large as it could -// grow; the caller then computes the missing tail on the fly. -static void grow_row(keystream_row_t *row, int cls, int need, const char *key, int key_length) { - if (need > KEYSTREAM_ROW_MAX) { - need = KEYSTREAM_ROW_MAX; - } - if (row->valid >= need) { - return; - } - if (row->cap < need) { - int newcap = (need + 255) & ~255; // round up to a 256-byte chunk - if (newcap > KEYSTREAM_ROW_MAX) { - newcap = KEYSTREAM_ROW_MAX; - } - uint8_t *p = realloc(row->data, newcap); - if (!p) { - return; // keep whatever we already have; tail handled by caller - } - row->data = p; - row->cap = newcap; - } - - uint8_t crc = row->crc; - int ki = row->valid % key_length; - for (int i = row->valid; i < need; i++) { - uint8_t inbyte = (uint8_t)(key[ki] + cls + key_length); - crc = crc_a[crc] ^ crc_b[inbyte]; - row->data[i] = crc; - if (++ki == key_length) { - ki = 0; - } - } - row->valid = need; - row->crc = crc; -} - -// XOR 'n' keystream bytes into dst, 8 bytes at a time. memcpy avoids both -// strict-aliasing violations and unaligned-access assumptions. -static void xor_block(uint8_t *dst, const uint8_t *ks, int n) { - int i = 0; - for (; i + 8 <= n; i += 8) { - uint64_t a, b; - memcpy(&a, dst + i, 8); - memcpy(&b, ks + i, 8); - a ^= b; - memcpy(dst + i, &a, 8); - } - for (; i < n; i++) { - dst[i] ^= ks[i]; - } -} - -void xor_data(uint8_t *buffer, int length, char *key, int key_length) { - if (length <= 0) { - return; - } - ensure_tables(); - - int cls = length & 0xFF; - keystream_row_t *row = &rows[cls]; - - int cached = (length <= KEYSTREAM_ROW_MAX) ? length : KEYSTREAM_ROW_MAX; - if (row->valid < cached) { - grow_row(row, cls, cached, key, key_length); - } - - int n = (length < row->valid) ? length : row->valid; - if (n > 0) { - xor_block(buffer, row->data, n); - } - - // Tail beyond the cache limit (or beyond what we could allocate): compute on - // the fly, continuing the CRC chain from the last cached state. - if (n < length) { - uint8_t crc = row->crc; - int ki = n % key_length; - for (int i = n; i < length; i++) { - uint8_t inbyte = (uint8_t)(key[ki] + cls + key_length); - crc = crc_a[crc] ^ crc_b[inbyte]; - buffer[i] ^= crc; - if (++ki == key_length) { - ki = 0; - } - } - } -} diff --git a/ios/Tunnel/obfuscator/obfuscation.h b/ios/Tunnel/obfuscator/obfuscation.h deleted file mode 100644 index e69a41a..0000000 --- a/ios/Tunnel/obfuscator/obfuscation.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef _OBFUSCATION_H_ -#define _OBFUSCATION_H_ - -#include - -// Current obfuscation version -#define OBFUSCATION_VERSION 1 - -// Maximum length (in bytes) of a single cached keystream row. -// The keystream depends only on (key, length mod 256), so it is cached in 256 -// lazily-grown rows. Packets longer than this limit are still handled correctly: -// the part beyond the limit is computed on the fly. Worst-case cache memory is -// 256 * KEYSTREAM_ROW_MAX bytes. 2048 covers a typical MTU plus masking overhead. -#define KEYSTREAM_ROW_MAX 2048 - -// WireGuard packet types -#define WG_TYPE_HANDSHAKE 0x01 -#define WG_TYPE_HANDSHAKE_RESP 0x02 -#define WG_TYPE_COOKIE 0x03 -#define WG_TYPE_DATA 0x04 - -#define WG_TYPE(data) ((uint32_t)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24))) -#ifndef MIN -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#endif - -/** - * Checks if the given data is obfuscated. - * - * @param data Pointer to the data buffer to check. - * @return uint8_t Returns a non-zero value if the data is obfuscated, 0 otherwise. - */ -static inline uint8_t is_obfuscated(uint8_t *data) { - uint32_t packet_type = WG_TYPE(data); - return !(packet_type >= 1 && packet_type <= 4); -} - -/** - * @brief XORs the data in the given buffer with the key-derived keystream. - * - * The keystream is a per-byte CRC8 value derived from the key, the byte position - * and the packet length; it depends only on (key, length mod 256), so it is - * cached (see obfuscation.c). The on-the-wire result is byte-for-byte identical - * to the original bit-by-bit implementation. - * - * @param buffer Pointer to the data buffer to be XORed. - * @param length Length of the data buffer in bytes. - * @param key Pointer to the key used for XOR operation. - * @param key_length Length of the key in bytes. - */ -void xor_data(uint8_t *buffer, int length, char *key, int key_length); - -/** - * @brief Encodes the given buffer using the specified key and version. - * - * This function applies an encoding algorithm to the input buffer using the provided key and version. - * WARNING: buffer must be at least 4 bytes long and aligned to 4 bytes. - * - * @param buffer Pointer to the data buffer to encode. - * @param length Length of the data buffer in bytes. - * @param key Pointer to the key used for encoding. - * @param key_length Length of the key in bytes. - * @param version Encoding version to use. - * @param max_dummy_length_data Maximum length of dummy data for data packets. - * @return 0 on success, or a negative value on error. - */ -static inline int encode(uint8_t *buffer, int length, char *key, int key_length, uint8_t version, int max_dummy_length_data) { - if (version >= 1) { - uint32_t packet_type = WG_TYPE(buffer); - // Add some randomness to the packet - uint8_t rnd = 1 + (rand() % 255); - buffer[0] ^= rnd; // Xor the first byte to a random value - buffer[1] = rnd; // Set the second byte to a random value - // Add dummy data to the packet - if (length < MAX_DUMMY_LENGTH_TOTAL) { - uint16_t dummy_length = 0; - uint16_t max_dummy_length = MAX_DUMMY_LENGTH_TOTAL - length; - switch (packet_type) { - case WG_TYPE_HANDSHAKE: - case WG_TYPE_HANDSHAKE_RESP: - // length to MAX_DUMMY_LENGTH_HANDSHAKE - dummy_length = rand() % MIN(max_dummy_length, MAX_DUMMY_LENGTH_HANDSHAKE); - break; - case WG_TYPE_COOKIE: - case WG_TYPE_DATA: - // length to MAX_DUMMY_LENGTH_HANDSHAKE - if (max_dummy_length_data) { - dummy_length = rand() % MIN(max_dummy_length, max_dummy_length_data); - } - break; - default: - //assert(0); - break; - } - buffer[2] = dummy_length & 0xFF; // Set the dummy length in the packet - buffer[3] = dummy_length >> 8; // Set the dummy length in - if (dummy_length > 0) { - int i = length; - length += dummy_length; - for (; i < length; ++i) { - buffer[i] = 0xFF; // Fill with FFs, random data is not needed - } - } - } - } - - xor_data(buffer, length, key, key_length); - - return length; -} - -/** - * Decodes the given buffer using the provided key. - * - * WARNING: buffer must be at least 4 bytes long and aligned to 4 bytes. - * - * @param buffer Pointer to the input buffer to decode. - * @param length Length of the input buffer. - * @param key Pointer to the key used for decoding. - * @param key_length Length of the key. - * @param version_out Pointer to a variable where the decoded version will be stored. - * @return Length of the decoded data (smaller than or equal to the input length). - */ -static inline int decode(uint8_t *buffer, int length, char *key, int key_length, uint8_t *version_out) { - xor_data(buffer, length, key, key_length); - - if (!is_obfuscated(buffer)) { - // Looks like an old version - *version_out = 0; - return length; - } - - buffer[0] ^= buffer[1]; // Restore the first byte by XORing it with the second byte - length -= (uint16_t)(buffer[2] | (buffer[3] << 8)); // Remove dummy data length from the packet - buffer[1] = buffer[2] = buffer[3] = 0; // Reset the dummy length field to 0 - return length; -} - -#endif // _OBFUSCATION_H_ \ No newline at end of file diff --git a/ios/Tunnel/obfuscator/uthash.h b/ios/Tunnel/obfuscator/uthash.h deleted file mode 100644 index 06c2eeb..0000000 --- a/ios/Tunnel/obfuscator/uthash.h +++ /dev/null @@ -1,1137 +0,0 @@ -/* -Copyright (c) 2003-2025, Troy D. Hanson https://troydhanson.github.io/uthash/ -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef UTHASH_H -#define UTHASH_H - -#define UTHASH_VERSION 2.3.0 - -#include /* memcmp, memset, strlen */ -#include /* ptrdiff_t */ -#include /* exit */ - -#if defined(HASH_NO_STDINT) && HASH_NO_STDINT -/* The user doesn't have , and must figure out their own way - to provide definitions for uint8_t and uint32_t. */ -#else -#include /* uint8_t, uint32_t */ -#endif - -/* These macros use decltype or the earlier __typeof GNU extension. - As decltype is only available in newer compilers (VS2010 or gcc 4.3+ - when compiling c++ source) this code uses whatever method is needed - or, for VS2008 where neither is available, uses casting workarounds. */ -#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) -#if defined(_MSC_VER) /* MS compiler */ -#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ -#define DECLTYPE(x) (decltype(x)) -#else /* VS2008 or older (or VS2010 in C mode) */ -#define NO_DECLTYPE -#endif -#elif defined(__MCST__) /* Elbrus C Compiler */ -#define DECLTYPE(x) (__typeof(x)) -#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) -#define NO_DECLTYPE -#else /* GNU, Sun and other compilers */ -#define DECLTYPE(x) (__typeof(x)) -#endif -#endif - -#ifdef NO_DECLTYPE -#define DECLTYPE(x) -#define DECLTYPE_ASSIGN(dst,src) \ -do { \ - char **_da_dst = (char**)(&(dst)); \ - *_da_dst = (char*)(src); \ -} while (0) -#else -#define DECLTYPE_ASSIGN(dst,src) \ -do { \ - (dst) = DECLTYPE(dst)(src); \ -} while (0) -#endif - -#ifndef uthash_malloc -#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ -#endif -#ifndef uthash_free -#define uthash_free(ptr,sz) free(ptr) /* free fcn */ -#endif -#ifndef uthash_bzero -#define uthash_bzero(a,n) memset(a,'\0',n) -#endif -#ifndef uthash_strlen -#define uthash_strlen(s) strlen(s) -#endif - -#ifndef HASH_FUNCTION -#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) -#endif - -#ifndef HASH_KEYCMP -#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) -#endif - -#ifndef uthash_noexpand_fyi -#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ -#endif -#ifndef uthash_expand_fyi -#define uthash_expand_fyi(tbl) /* can be defined to log expands */ -#endif - -#ifndef HASH_NONFATAL_OOM -#define HASH_NONFATAL_OOM 0 -#endif - -#if HASH_NONFATAL_OOM -/* malloc failures can be recovered from */ - -#ifndef uthash_nonfatal_oom -#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ -#endif - -#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) -#define IF_HASH_NONFATAL_OOM(x) x - -#else -/* malloc failures result in lost memory, hash tables are unusable */ - -#ifndef uthash_fatal -#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ -#endif - -#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") -#define IF_HASH_NONFATAL_OOM(x) - -#endif - -/* initial number of buckets */ -#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ -#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ -#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ - -/* calculate the element whose hash handle address is hhp */ -#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) -/* calculate the hash handle from element address elp */ -#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) - -#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ -do { \ - struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ - unsigned _hd_bkt; \ - HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ - (head)->hh.tbl->buckets[_hd_bkt].count++; \ - _hd_hh_item->hh_next = NULL; \ - _hd_hh_item->hh_prev = NULL; \ -} while (0) - -#define HASH_VALUE(keyptr,keylen,hashv) \ -do { \ - HASH_FUNCTION(keyptr, keylen, hashv); \ -} while (0) - -#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ -do { \ - (out) = NULL; \ - if (head) { \ - unsigned _hf_bkt; \ - HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ - if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ - HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ - } \ - } \ -} while (0) - -#define HASH_FIND(hh,head,keyptr,keylen,out) \ -do { \ - (out) = NULL; \ - if (head) { \ - unsigned _hf_hashv; \ - HASH_VALUE(keyptr, keylen, _hf_hashv); \ - HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ - } \ -} while (0) - -#ifdef HASH_BLOOM -#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) -#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) -#define HASH_BLOOM_MAKE(tbl,oomed) \ -do { \ - (tbl)->bloom_nbits = HASH_BLOOM; \ - (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ - if (!(tbl)->bloom_bv) { \ - HASH_RECORD_OOM(oomed); \ - } else { \ - uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ - (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ - } \ -} while (0) - -#define HASH_BLOOM_FREE(tbl) \ -do { \ - uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ -} while (0) - -#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) -#define HASH_BLOOM_BITTEST(bv,idx) ((bv[(idx)/8U] & (1U << ((idx)%8U))) != 0) - -#define HASH_BLOOM_ADD(tbl,hashv) \ - HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) - -#define HASH_BLOOM_TEST(tbl,hashv) \ - HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) - -#else -#define HASH_BLOOM_MAKE(tbl,oomed) -#define HASH_BLOOM_FREE(tbl) -#define HASH_BLOOM_ADD(tbl,hashv) -#define HASH_BLOOM_TEST(tbl,hashv) 1 -#define HASH_BLOOM_BYTELEN 0U -#endif - -#define HASH_MAKE_TABLE(hh,head,oomed) \ -do { \ - (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ - if (!(head)->hh.tbl) { \ - HASH_RECORD_OOM(oomed); \ - } else { \ - uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ - (head)->hh.tbl->tail = &((head)->hh); \ - (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ - (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ - (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ - (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ - HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ - (head)->hh.tbl->signature = HASH_SIGNATURE; \ - if (!(head)->hh.tbl->buckets) { \ - HASH_RECORD_OOM(oomed); \ - uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ - } else { \ - uthash_bzero((head)->hh.tbl->buckets, \ - HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ - HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ - IF_HASH_NONFATAL_OOM( \ - if (oomed) { \ - uthash_free((head)->hh.tbl->buckets, \ - HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ - uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ - } \ - ) \ - } \ - } \ -} while (0) - -#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ -do { \ - (replaced) = NULL; \ - HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ - if (replaced) { \ - HASH_DELETE(hh, head, replaced); \ - } \ - HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ -} while (0) - -#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ -do { \ - (replaced) = NULL; \ - HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ - if (replaced) { \ - HASH_DELETE(hh, head, replaced); \ - } \ - HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ -} while (0) - -#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ -do { \ - unsigned _hr_hashv; \ - HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ - HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ -} while (0) - -#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ -do { \ - unsigned _hr_hashv; \ - HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ - HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ -} while (0) - -#define HASH_APPEND_LIST(hh, head, add) \ -do { \ - (add)->hh.next = NULL; \ - (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ - (head)->hh.tbl->tail->next = (add); \ - (head)->hh.tbl->tail = &((add)->hh); \ -} while (0) - -#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ -do { \ - do { \ - if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ - break; \ - } \ - } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ -} while (0) - -#ifdef NO_DECLTYPE -#undef HASH_AKBI_INNER_LOOP -#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ -do { \ - char *_hs_saved_head = (char*)(head); \ - do { \ - DECLTYPE_ASSIGN(head, _hs_iter); \ - if (cmpfcn(head, add) > 0) { \ - DECLTYPE_ASSIGN(head, _hs_saved_head); \ - break; \ - } \ - DECLTYPE_ASSIGN(head, _hs_saved_head); \ - } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ -} while (0) -#endif - -#if HASH_NONFATAL_OOM - -#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ -do { \ - if (!(oomed)) { \ - unsigned _ha_bkt; \ - (head)->hh.tbl->num_items++; \ - HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ - HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ - if (oomed) { \ - HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ - HASH_DELETE_HH(hh, head, &(add)->hh); \ - (add)->hh.tbl = NULL; \ - uthash_nonfatal_oom(add); \ - } else { \ - HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ - HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ - } \ - } else { \ - (add)->hh.tbl = NULL; \ - uthash_nonfatal_oom(add); \ - } \ -} while (0) - -#else - -#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ -do { \ - unsigned _ha_bkt; \ - (head)->hh.tbl->num_items++; \ - HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ - HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ - HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ - HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ -} while (0) - -#endif - - -#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ -do { \ - IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ - (add)->hh.hashv = (hashval); \ - (add)->hh.key = (char*) (keyptr); \ - (add)->hh.keylen = (unsigned) (keylen_in); \ - if (!(head)) { \ - (add)->hh.next = NULL; \ - (add)->hh.prev = NULL; \ - HASH_MAKE_TABLE(hh, add, _ha_oomed); \ - IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ - (head) = (add); \ - IF_HASH_NONFATAL_OOM( } ) \ - } else { \ - void *_hs_iter = (head); \ - (add)->hh.tbl = (head)->hh.tbl; \ - HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ - if (_hs_iter) { \ - (add)->hh.next = _hs_iter; \ - if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ - HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ - } else { \ - (head) = (add); \ - } \ - HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ - } else { \ - HASH_APPEND_LIST(hh, head, add); \ - } \ - } \ - HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ - HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ -} while (0) - -#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ -do { \ - unsigned _hs_hashv; \ - HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ - HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ -} while (0) - -#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ - HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) - -#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ - HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) - -#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ -do { \ - IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ - (add)->hh.hashv = (hashval); \ - (add)->hh.key = (const void*) (keyptr); \ - (add)->hh.keylen = (unsigned) (keylen_in); \ - if (!(head)) { \ - (add)->hh.next = NULL; \ - (add)->hh.prev = NULL; \ - HASH_MAKE_TABLE(hh, add, _ha_oomed); \ - IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ - (head) = (add); \ - IF_HASH_NONFATAL_OOM( } ) \ - } else { \ - (add)->hh.tbl = (head)->hh.tbl; \ - HASH_APPEND_LIST(hh, head, add); \ - } \ - HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ - HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ -} while (0) - -#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ -do { \ - unsigned _ha_hashv; \ - HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ - HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ -} while (0) - -#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ - HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) - -#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ - HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) - -#define HASH_TO_BKT(hashv,num_bkts,bkt) \ -do { \ - bkt = ((hashv) & ((num_bkts) - 1U)); \ -} while (0) - -/* delete "delptr" from the hash table. - * "the usual" patch-up process for the app-order doubly-linked-list. - * The use of _hd_hh_del below deserves special explanation. - * These used to be expressed using (delptr) but that led to a bug - * if someone used the same symbol for the head and deletee, like - * HASH_DELETE(hh,users,users); - * We want that to work, but by changing the head (users) below - * we were forfeiting our ability to further refer to the deletee (users) - * in the patch-up process. Solution: use scratch space to - * copy the deletee pointer, then the latter references are via that - * scratch pointer rather than through the repointed (users) symbol. - */ -#define HASH_DELETE(hh,head,delptr) \ - HASH_DELETE_HH(hh, head, &(delptr)->hh) - -#define HASH_DELETE_HH(hh,head,delptrhh) \ -do { \ - const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ - if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ - HASH_BLOOM_FREE((head)->hh.tbl); \ - uthash_free((head)->hh.tbl->buckets, \ - (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ - uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ - (head) = NULL; \ - } else { \ - unsigned _hd_bkt; \ - if (_hd_hh_del == (head)->hh.tbl->tail) { \ - (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ - } \ - if (_hd_hh_del->prev != NULL) { \ - HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ - } else { \ - DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ - } \ - if (_hd_hh_del->next != NULL) { \ - HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ - } \ - HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ - HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ - (head)->hh.tbl->num_items--; \ - } \ - HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ -} while (0) - -/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ -#define HASH_FIND_STR(head,findstr,out) \ -do { \ - unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ - HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ -} while (0) -#define HASH_ADD_STR(head,strfield,add) \ -do { \ - unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ - HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ -} while (0) -#define HASH_REPLACE_STR(head,strfield,add,replaced) \ -do { \ - unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ - HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ -} while (0) -#define HASH_FIND_INT(head,findint,out) \ - HASH_FIND(hh,head,findint,sizeof(int),out) -#define HASH_ADD_INT(head,intfield,add) \ - HASH_ADD(hh,head,intfield,sizeof(int),add) -#define HASH_REPLACE_INT(head,intfield,add,replaced) \ - HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) -#define HASH_FIND_PTR(head,findptr,out) \ - HASH_FIND(hh,head,findptr,sizeof(void *),out) -#define HASH_ADD_PTR(head,ptrfield,add) \ - HASH_ADD(hh,head,ptrfield,sizeof(void *),add) -#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ - HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) -#define HASH_DEL(head,delptr) \ - HASH_DELETE(hh,head,delptr) - -/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. - * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. - */ -#ifdef HASH_DEBUG -#include /* fprintf, stderr */ -#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) -#define HASH_FSCK(hh,head,where) \ -do { \ - struct UT_hash_handle *_thh; \ - if (head) { \ - unsigned _bkt_i; \ - unsigned _count = 0; \ - char *_prev; \ - for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ - unsigned _bkt_count = 0; \ - _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ - _prev = NULL; \ - while (_thh) { \ - if (_prev != (char*)(_thh->hh_prev)) { \ - HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ - (where), (void*)_thh->hh_prev, (void*)_prev); \ - } \ - _bkt_count++; \ - _prev = (char*)(_thh); \ - _thh = _thh->hh_next; \ - } \ - _count += _bkt_count; \ - if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ - HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ - (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ - } \ - } \ - if (_count != (head)->hh.tbl->num_items) { \ - HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ - (where), (head)->hh.tbl->num_items, _count); \ - } \ - _count = 0; \ - _prev = NULL; \ - _thh = &(head)->hh; \ - while (_thh) { \ - _count++; \ - if (_prev != (char*)_thh->prev) { \ - HASH_OOPS("%s: invalid prev %p, actual %p\n", \ - (where), (void*)_thh->prev, (void*)_prev); \ - } \ - _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ - _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ - } \ - if (_count != (head)->hh.tbl->num_items) { \ - HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ - (where), (head)->hh.tbl->num_items, _count); \ - } \ - } \ -} while (0) -#else -#define HASH_FSCK(hh,head,where) -#endif - -/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to - * the descriptor to which this macro is defined for tuning the hash function. - * The app can #include to get the prototype for write(2). */ -#ifdef HASH_EMIT_KEYS -#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ -do { \ - unsigned _klen = fieldlen; \ - write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ - write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ -} while (0) -#else -#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) -#endif - -/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ -#define HASH_BER(key,keylen,hashv) \ -do { \ - unsigned _hb_keylen = (unsigned)keylen; \ - const unsigned char *_hb_key = (const unsigned char*)(key); \ - (hashv) = 0; \ - while (_hb_keylen-- != 0U) { \ - (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ - } \ -} while (0) - - -/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at - * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx - * (archive link: https://archive.is/Ivcan ) - */ -#define HASH_SAX(key,keylen,hashv) \ -do { \ - unsigned _sx_i; \ - const unsigned char *_hs_key = (const unsigned char*)(key); \ - hashv = 0; \ - for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ - hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ - } \ -} while (0) -/* FNV-1a variation */ -#define HASH_FNV(key,keylen,hashv) \ -do { \ - unsigned _fn_i; \ - const unsigned char *_hf_key = (const unsigned char*)(key); \ - (hashv) = 2166136261U; \ - for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ - hashv = hashv ^ _hf_key[_fn_i]; \ - hashv = hashv * 16777619U; \ - } \ -} while (0) - -#define HASH_OAT(key,keylen,hashv) \ -do { \ - unsigned _ho_i; \ - const unsigned char *_ho_key=(const unsigned char*)(key); \ - hashv = 0; \ - for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ - hashv += _ho_key[_ho_i]; \ - hashv += (hashv << 10); \ - hashv ^= (hashv >> 6); \ - } \ - hashv += (hashv << 3); \ - hashv ^= (hashv >> 11); \ - hashv += (hashv << 15); \ -} while (0) - -#define HASH_JEN_MIX(a,b,c) \ -do { \ - a -= b; a -= c; a ^= ( c >> 13 ); \ - b -= c; b -= a; b ^= ( a << 8 ); \ - c -= a; c -= b; c ^= ( b >> 13 ); \ - a -= b; a -= c; a ^= ( c >> 12 ); \ - b -= c; b -= a; b ^= ( a << 16 ); \ - c -= a; c -= b; c ^= ( b >> 5 ); \ - a -= b; a -= c; a ^= ( c >> 3 ); \ - b -= c; b -= a; b ^= ( a << 10 ); \ - c -= a; c -= b; c ^= ( b >> 15 ); \ -} while (0) - -#define HASH_JEN(key,keylen,hashv) \ -do { \ - unsigned _hj_i,_hj_j,_hj_k; \ - unsigned const char *_hj_key=(unsigned const char*)(key); \ - hashv = 0xfeedbeefu; \ - _hj_i = _hj_j = 0x9e3779b9u; \ - _hj_k = (unsigned)(keylen); \ - while (_hj_k >= 12U) { \ - _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ - + ( (unsigned)_hj_key[2] << 16 ) \ - + ( (unsigned)_hj_key[3] << 24 ) ); \ - _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ - + ( (unsigned)_hj_key[6] << 16 ) \ - + ( (unsigned)_hj_key[7] << 24 ) ); \ - hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ - + ( (unsigned)_hj_key[10] << 16 ) \ - + ( (unsigned)_hj_key[11] << 24 ) ); \ - \ - HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ - \ - _hj_key += 12; \ - _hj_k -= 12U; \ - } \ - hashv += (unsigned)(keylen); \ - switch ( _hj_k ) { \ - case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ - case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ - case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ - case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ - case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ - case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ - case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ - case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ - case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ - case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ - case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ - default: ; \ - } \ - HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ -} while (0) - -/* The Paul Hsieh hash function */ -#undef get16bits -#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ - || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) -#define get16bits(d) (*((const uint16_t *) (d))) -#endif - -#if !defined (get16bits) -#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ - +(uint32_t)(((const uint8_t *)(d))[0]) ) -#endif -#define HASH_SFH(key,keylen,hashv) \ -do { \ - unsigned const char *_sfh_key=(unsigned const char*)(key); \ - uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ - \ - unsigned _sfh_rem = _sfh_len & 3U; \ - _sfh_len >>= 2; \ - hashv = 0xcafebabeu; \ - \ - /* Main loop */ \ - for (;_sfh_len > 0U; _sfh_len--) { \ - hashv += get16bits (_sfh_key); \ - _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ - hashv = (hashv << 16) ^ _sfh_tmp; \ - _sfh_key += 2U*sizeof (uint16_t); \ - hashv += hashv >> 11; \ - } \ - \ - /* Handle end cases */ \ - switch (_sfh_rem) { \ - case 3: hashv += get16bits (_sfh_key); \ - hashv ^= hashv << 16; \ - hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ - hashv += hashv >> 11; \ - break; \ - case 2: hashv += get16bits (_sfh_key); \ - hashv ^= hashv << 11; \ - hashv += hashv >> 17; \ - break; \ - case 1: hashv += *_sfh_key; \ - hashv ^= hashv << 10; \ - hashv += hashv >> 1; \ - break; \ - default: ; \ - } \ - \ - /* Force "avalanching" of final 127 bits */ \ - hashv ^= hashv << 3; \ - hashv += hashv >> 5; \ - hashv ^= hashv << 4; \ - hashv += hashv >> 17; \ - hashv ^= hashv << 25; \ - hashv += hashv >> 6; \ -} while (0) - -/* iterate over items in a known bucket to find desired item */ -#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ -do { \ - if ((head).hh_head != NULL) { \ - DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ - } else { \ - (out) = NULL; \ - } \ - while ((out) != NULL) { \ - if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ - if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ - break; \ - } \ - } \ - if ((out)->hh.hh_next != NULL) { \ - DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ - } else { \ - (out) = NULL; \ - } \ - } \ -} while (0) - -/* add an item to a bucket */ -#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ -do { \ - UT_hash_bucket *_ha_head = &(head); \ - _ha_head->count++; \ - (addhh)->hh_next = _ha_head->hh_head; \ - (addhh)->hh_prev = NULL; \ - if (_ha_head->hh_head != NULL) { \ - _ha_head->hh_head->hh_prev = (addhh); \ - } \ - _ha_head->hh_head = (addhh); \ - if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ - && !(addhh)->tbl->noexpand) { \ - HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ - IF_HASH_NONFATAL_OOM( \ - if (oomed) { \ - HASH_DEL_IN_BKT(head,addhh); \ - } \ - ) \ - } \ -} while (0) - -/* remove an item from a given bucket */ -#define HASH_DEL_IN_BKT(head,delhh) \ -do { \ - UT_hash_bucket *_hd_head = &(head); \ - _hd_head->count--; \ - if (_hd_head->hh_head == (delhh)) { \ - _hd_head->hh_head = (delhh)->hh_next; \ - } \ - if ((delhh)->hh_prev) { \ - (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ - } \ - if ((delhh)->hh_next) { \ - (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ - } \ -} while (0) - -/* Bucket expansion has the effect of doubling the number of buckets - * and redistributing the items into the new buckets. Ideally the - * items will distribute more or less evenly into the new buckets - * (the extent to which this is true is a measure of the quality of - * the hash function as it applies to the key domain). - * - * With the items distributed into more buckets, the chain length - * (item count) in each bucket is reduced. Thus by expanding buckets - * the hash keeps a bound on the chain length. This bounded chain - * length is the essence of how a hash provides constant time lookup. - * - * The calculation of tbl->ideal_chain_maxlen below deserves some - * explanation. First, keep in mind that we're calculating the ideal - * maximum chain length based on the *new* (doubled) bucket count. - * In fractions this is just n/b (n=number of items,b=new num buckets). - * Since the ideal chain length is an integer, we want to calculate - * ceil(n/b). We don't depend on floating point arithmetic in this - * hash, so to calculate ceil(n/b) with integers we could write - * - * ceil(n/b) = (n/b) + ((n%b)?1:0) - * - * and in fact a previous version of this hash did just that. - * But now we have improved things a bit by recognizing that b is - * always a power of two. We keep its base 2 log handy (call it lb), - * so now we can write this with a bit shift and logical AND: - * - * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) - * - */ -#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ -do { \ - unsigned _he_bkt; \ - unsigned _he_bkt_i; \ - struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ - UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ - _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ - sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ - if (!_he_new_buckets) { \ - HASH_RECORD_OOM(oomed); \ - } else { \ - uthash_bzero(_he_new_buckets, \ - sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ - (tbl)->ideal_chain_maxlen = \ - ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ - ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ - (tbl)->nonideal_items = 0; \ - for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ - _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ - while (_he_thh != NULL) { \ - _he_hh_nxt = _he_thh->hh_next; \ - HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ - _he_newbkt = &(_he_new_buckets[_he_bkt]); \ - if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ - (tbl)->nonideal_items++; \ - if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ - _he_newbkt->expand_mult++; \ - } \ - } \ - _he_thh->hh_prev = NULL; \ - _he_thh->hh_next = _he_newbkt->hh_head; \ - if (_he_newbkt->hh_head != NULL) { \ - _he_newbkt->hh_head->hh_prev = _he_thh; \ - } \ - _he_newbkt->hh_head = _he_thh; \ - _he_thh = _he_hh_nxt; \ - } \ - } \ - uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ - (tbl)->num_buckets *= 2U; \ - (tbl)->log2_num_buckets++; \ - (tbl)->buckets = _he_new_buckets; \ - (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ - ((tbl)->ineff_expands+1U) : 0U; \ - if ((tbl)->ineff_expands > 1U) { \ - (tbl)->noexpand = 1; \ - uthash_noexpand_fyi(tbl); \ - } \ - uthash_expand_fyi(tbl); \ - } \ -} while (0) - - -/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ -/* Note that HASH_SORT assumes the hash handle name to be hh. - * HASH_SRT was added to allow the hash handle name to be passed in. */ -#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) -#define HASH_SRT(hh,head,cmpfcn) \ -do { \ - unsigned _hs_i; \ - unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ - struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ - if (head != NULL) { \ - _hs_insize = 1; \ - _hs_looping = 1; \ - _hs_list = &((head)->hh); \ - while (_hs_looping != 0U) { \ - _hs_p = _hs_list; \ - _hs_list = NULL; \ - _hs_tail = NULL; \ - _hs_nmerges = 0; \ - while (_hs_p != NULL) { \ - _hs_nmerges++; \ - _hs_q = _hs_p; \ - _hs_psize = 0; \ - for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ - _hs_psize++; \ - _hs_q = ((_hs_q->next != NULL) ? \ - HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ - if (_hs_q == NULL) { \ - break; \ - } \ - } \ - _hs_qsize = _hs_insize; \ - while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ - if (_hs_psize == 0U) { \ - _hs_e = _hs_q; \ - _hs_q = ((_hs_q->next != NULL) ? \ - HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ - _hs_qsize--; \ - } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ - _hs_e = _hs_p; \ - if (_hs_p != NULL) { \ - _hs_p = ((_hs_p->next != NULL) ? \ - HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ - } \ - _hs_psize--; \ - } else if ((cmpfcn( \ - DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ - DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ - )) <= 0) { \ - _hs_e = _hs_p; \ - if (_hs_p != NULL) { \ - _hs_p = ((_hs_p->next != NULL) ? \ - HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ - } \ - _hs_psize--; \ - } else { \ - _hs_e = _hs_q; \ - _hs_q = ((_hs_q->next != NULL) ? \ - HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ - _hs_qsize--; \ - } \ - if ( _hs_tail != NULL ) { \ - _hs_tail->next = ((_hs_e != NULL) ? \ - ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ - } else { \ - _hs_list = _hs_e; \ - } \ - if (_hs_e != NULL) { \ - _hs_e->prev = ((_hs_tail != NULL) ? \ - ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ - } \ - _hs_tail = _hs_e; \ - } \ - _hs_p = _hs_q; \ - } \ - if (_hs_tail != NULL) { \ - _hs_tail->next = NULL; \ - } \ - if (_hs_nmerges <= 1U) { \ - _hs_looping = 0; \ - (head)->hh.tbl->tail = _hs_tail; \ - DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ - } \ - _hs_insize *= 2U; \ - } \ - HASH_FSCK(hh, head, "HASH_SRT"); \ - } \ -} while (0) - -/* This function selects items from one hash into another hash. - * The end result is that the selected items have dual presence - * in both hashes. There is no copy of the items made; rather - * they are added into the new hash through a secondary hash - * hash handle that must be present in the structure. */ -#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ -do { \ - unsigned _src_bkt, _dst_bkt; \ - void *_last_elt = NULL, *_elt; \ - UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ - ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ - if ((src) != NULL) { \ - for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ - for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ - _src_hh != NULL; \ - _src_hh = _src_hh->hh_next) { \ - _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ - if (cond(_elt)) { \ - IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ - _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ - _dst_hh->key = _src_hh->key; \ - _dst_hh->keylen = _src_hh->keylen; \ - _dst_hh->hashv = _src_hh->hashv; \ - _dst_hh->prev = _last_elt; \ - _dst_hh->next = NULL; \ - if (_last_elt_hh != NULL) { \ - _last_elt_hh->next = _elt; \ - } \ - if ((dst) == NULL) { \ - DECLTYPE_ASSIGN(dst, _elt); \ - HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ - IF_HASH_NONFATAL_OOM( \ - if (_hs_oomed) { \ - uthash_nonfatal_oom(_elt); \ - (dst) = NULL; \ - continue; \ - } \ - ) \ - } else { \ - _dst_hh->tbl = (dst)->hh_dst.tbl; \ - } \ - HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ - HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ - (dst)->hh_dst.tbl->num_items++; \ - IF_HASH_NONFATAL_OOM( \ - if (_hs_oomed) { \ - HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ - HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ - _dst_hh->tbl = NULL; \ - uthash_nonfatal_oom(_elt); \ - continue; \ - } \ - ) \ - HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ - _last_elt = _elt; \ - _last_elt_hh = _dst_hh; \ - } \ - } \ - } \ - } \ - HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ -} while (0) - -#define HASH_CLEAR(hh,head) \ -do { \ - if ((head) != NULL) { \ - HASH_BLOOM_FREE((head)->hh.tbl); \ - uthash_free((head)->hh.tbl->buckets, \ - (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ - uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ - (head) = NULL; \ - } \ -} while (0) - -#define HASH_OVERHEAD(hh,head) \ - (((head) != NULL) ? ( \ - (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ - ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ - sizeof(UT_hash_table) + \ - (HASH_BLOOM_BYTELEN))) : 0U) - -#ifdef NO_DECLTYPE -#define HASH_ITER(hh,head,el,tmp) \ -for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ - (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) -#else -#define HASH_ITER(hh,head,el,tmp) \ -for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ - (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) -#endif - -/* obtain a count of items in the hash */ -#define HASH_COUNT(head) HASH_CNT(hh,head) -#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) - -typedef struct UT_hash_bucket { - struct UT_hash_handle *hh_head; - unsigned count; - - /* expand_mult is normally set to 0. In this situation, the max chain length - * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If - * the bucket's chain exceeds this length, bucket expansion is triggered). - * However, setting expand_mult to a non-zero value delays bucket expansion - * (that would be triggered by additions to this particular bucket) - * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. - * (The multiplier is simply expand_mult+1). The whole idea of this - * multiplier is to reduce bucket expansions, since they are expensive, in - * situations where we know that a particular bucket tends to be overused. - * It is better to let its chain length grow to a longer yet-still-bounded - * value, than to do an O(n) bucket expansion too often. - */ - unsigned expand_mult; - -} UT_hash_bucket; - -/* random signature used only to find hash tables in external analysis */ -#define HASH_SIGNATURE 0xa0111fe1u -#define HASH_BLOOM_SIGNATURE 0xb12220f2u - -typedef struct UT_hash_table { - UT_hash_bucket *buckets; - unsigned num_buckets, log2_num_buckets; - unsigned num_items; - struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ - ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ - - /* in an ideal situation (all buckets used equally), no bucket would have - * more than ceil(#items/#buckets) items. that's the ideal chain length. */ - unsigned ideal_chain_maxlen; - - /* nonideal_items is the number of items in the hash whose chain position - * exceeds the ideal chain maxlen. these items pay the penalty for an uneven - * hash distribution; reaching them in a chain traversal takes >ideal steps */ - unsigned nonideal_items; - - /* ineffective expands occur when a bucket doubling was performed, but - * afterward, more than half the items in the hash had nonideal chain - * positions. If this happens on two consecutive expansions we inhibit any - * further expansion, as it's not helping; this happens when the hash - * function isn't a good fit for the key domain. When expansion is inhibited - * the hash will still work, albeit no longer in constant time. */ - unsigned ineff_expands, noexpand; - - uint32_t signature; /* used only to find hash tables in external analysis */ -#ifdef HASH_BLOOM - uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ - uint8_t *bloom_bv; - uint8_t bloom_nbits; -#endif - -} UT_hash_table; - -typedef struct UT_hash_handle { - struct UT_hash_table *tbl; - void *prev; /* prev element in app order */ - void *next; /* next element in app order */ - struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ - struct UT_hash_handle *hh_next; /* next hh in bucket order */ - const void *key; /* ptr to enclosing struct's key */ - unsigned keylen; /* enclosing struct's key len */ - unsigned hashv; /* result of hash-fcn(key) */ -} UT_hash_handle; - -#endif /* UTHASH_H */ diff --git a/ios/Tunnel/obfuscator/wg-obfuscator.c b/ios/Tunnel/obfuscator/wg-obfuscator.c deleted file mode 100644 index 9470d38..0000000 --- a/ios/Tunnel/obfuscator/wg-obfuscator.c +++ /dev/null @@ -1,1320 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "wg-obfuscator.h" -#include "config.h" -#include "obfuscation.h" -#include "uthash.h" -#include "masking.h" - -// Verbosity level -int verbose = LL_DEFAULT; -// Section name (for multiple instances) -char section_name[256] = DEFAULT_INSTANCE_NAME; -// Listening socket for receiving data from the clients -static int listen_sock = 0; -// Hash table for client connections -static client_entry_t *conn_table = NULL; -// PIDs of the other instances, filled by the parent process only -static pid_t *child_pids = NULL; -static int child_pids_count = 0; -// Set by the SIGHUP handler, the log file is reopened from the main loop -static volatile sig_atomic_t log_reopen_pending = 0; - -// Hostname re-resolve: the blocking getaddrinfo() runs in a helper thread -#define RESOLVE_TAG_TARGET (-1) -typedef struct { - int32_t tag; // RESOLVE_TAG_TARGET or index into resolve_bindings - int32_t err; // getaddrinfo() error, 0 on success - uint32_t addr; // IPv4 address in network byte order -} resolve_result_t; - -static int resolve_wake_rd = -1; -static int resolve_wake_wr = -1; -static int resolve_result_rd = -1; -static int resolve_result_wr = -1; -static char resolve_target_host[256]; -static int resolve_target_is_name = 0; -static long resolve_interval_ms = 0; -static client_entry_t **resolve_bindings = NULL; -static int resolve_bindings_count = 0; - -#ifdef USE_EPOLL - static int epfd = 0; -#endif - -/** - * @brief Handles incoming signals for the application. - * - * This function is registered as a signal handler and is invoked when the process - * receives a signal. The specific actions taken depend on the signal received. - * - * @param signal The signal number received by the process. - */ -static void signal_handler(int signal) { - client_entry_t *current_entry, *tmp; - - // Close all connections and clean up - if (listen_sock) { - close(listen_sock); - } - HASH_ITER(hh, conn_table, current_entry, tmp) { - if (current_entry->server_sock) { - close(current_entry->server_sock); - } - HASH_DEL(conn_table, current_entry); - free(current_entry); - } -#ifdef USE_EPOLL - if (epfd) { - close(epfd); - } -#endif - log(LL_INFO, "Stopped."); - exit(signal != -1 ? EXIT_SUCCESS : EXIT_FAILURE); -} -#define FAILURE() signal_handler(-1) - -/** - * @brief Returns 1 if the string is a dotted IPv4 address, 0 otherwise. - */ -static int is_ipv4_literal(const char *host) -{ - struct in_addr tmp; - return host && *host && inet_pton(AF_INET, host, &tmp) == 1; -} - -/** - * @brief Resolves a hostname to an IPv4 address. - * - * @param host Hostname or IPv4 literal. - * @param out Filled with the first IPv4 address on success. - * @return 0 on success, a getaddrinfo() error code otherwise. - */ -static int resolve_hostname(const char *host, struct in_addr *out) -{ - struct addrinfo hints = { - .ai_family = AF_INET, - .ai_socktype = SOCK_DGRAM, - }; - struct addrinfo *addr = NULL; - int err = getaddrinfo(host, NULL, &hints, &addr); - if (err != 0 || addr == NULL) { - if (addr) { - freeaddrinfo(addr); - } - return err ? err : EAI_NONAME; - } - *out = ((struct sockaddr_in *)addr->ai_addr)->sin_addr; - freeaddrinfo(addr); - return 0; -} - -/** - * @brief Resolves a hostname at startup. - * - * On failure: if interval_ms is 0, returns the error immediately. Otherwise - * retries every interval_ms until the name resolves, so the daemon can start - * before the network or DNS is up. Sleep is interruptible by SIGINT/SIGTERM. - * - * @param host Hostname or IPv4 literal. - * @param out Filled with the first IPv4 address on success. - * @param interval_ms Retry interval in milliseconds, 0 to fail immediately. - * @param what Short label for the log, e.g. "target" or "static binding". - * @return 0 on success, a getaddrinfo() error code if interval_ms is 0 and resolve failed. - */ -static int resolve_hostname_startup(const char *host, struct in_addr *out, long interval_ms, const char *what) -{ - while (1) { - int err = resolve_hostname(host, out); - if (err == 0) { - return 0; - } - if (interval_ms <= 0) { - return err; - } - log(LL_WARN, "Can't resolve %s hostname '%s': %s, retrying in %ld seconds", - what, host, gai_strerror(err), interval_ms / 1000); - struct timespec ts = { - .tv_sec = interval_ms / 1000, - .tv_nsec = (interval_ms % 1000) * 1000000L, - }; - while (nanosleep(&ts, &ts) < 0 && errno == EINTR) { - } - } -} - -/** - * @brief Sends one resolve result to the main thread. The write is at most - * PIPE_BUF bytes, so it is atomic and cannot interleave with another result. - */ -static void resolve_send_result(int32_t tag, int32_t err, uint32_t addr) -{ - resolve_result_t r = { .tag = tag, .err = err, .addr = addr }; - const char *p = (const char *)&r; - size_t left = sizeof(r); - while (left) { - ssize_t n = write(resolve_result_wr, p, left); - if (n < 0) { - if (errno == EINTR) { - continue; - } - return; - } - p += n; - left -= (size_t)n; - } -} - -static void resolve_one(int32_t tag, const char *host) -{ - struct in_addr addr; - int err = resolve_hostname(host, &addr); - resolve_send_result(tag, err, err ? 0 : addr.s_addr); -} - -/** - * @brief Resolver thread: waits for SIGHUP (wake pipe) or the refresh interval, - * then re-resolves every non-literal hostname. Never calls log(). - */ -static void *resolve_thread_fn(void *arg) -{ - (void)arg; - struct pollfd pfd = { .fd = resolve_wake_rd, .events = POLLIN }; - while (1) { - int timeout = resolve_interval_ms > 0 ? (int)resolve_interval_ms : -1; - int ret = poll(&pfd, 1, timeout); - if (ret < 0) { - if (errno == EINTR) { - continue; - } - continue; - } - if (ret > 0 && (pfd.revents & POLLIN)) { - char buf[64]; - while (read(resolve_wake_rd, buf, sizeof(buf)) > 0) { - } - } - if (resolve_target_is_name) { - resolve_one(RESOLVE_TAG_TARGET, resolve_target_host); - } - for (int i = 0; i < resolve_bindings_count; i++) { - if (resolve_bindings[i] && resolve_bindings[i]->bind_host[0]) { - resolve_one(i, resolve_bindings[i]->bind_host); - } - } - } - return NULL; -} - -/** - * @brief Wakes the resolver thread. Safe to call from a signal handler. - */ -static void resolve_wake(void) -{ - char c = 1; - if (resolve_wake_wr < 0) { - return; - } - if (write(resolve_wake_wr, &c, 1) < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { - // Nothing useful can be done from a signal handler - } -} - -/** - * @brief Applies one re-resolve result on the main thread. - */ -static void apply_resolve_result(const resolve_result_t *r, struct sockaddr_in *forward_addr) -{ - if (r->err != 0) { - const char *name = "?"; - if (r->tag == RESOLVE_TAG_TARGET) { - name = resolve_target_host; - } else if (r->tag >= 0 && r->tag < resolve_bindings_count && resolve_bindings[r->tag]) { - name = resolve_bindings[r->tag]->bind_host; - } - log(LL_WARN, "Can't re-resolve hostname '%s': %s", name, gai_strerror(r->err)); - return; - } - - if (r->tag == RESOLVE_TAG_TARGET) { - if (forward_addr->sin_addr.s_addr == r->addr) { - return; - } - char old_ip[INET_ADDRSTRLEN], new_ip[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &forward_addr->sin_addr, old_ip, sizeof(old_ip)); - forward_addr->sin_addr.s_addr = r->addr; - inet_ntop(AF_INET, &forward_addr->sin_addr, new_ip, sizeof(new_ip)); - log(LL_INFO, "Target address changed: %s -> %s", old_ip, new_ip); - - client_entry_t *e, *tmp; - HASH_ITER(hh, conn_table, e, tmp) { - if (connect(e->server_sock, (struct sockaddr *)forward_addr, sizeof(*forward_addr)) < 0) { - serror_level(LL_WARN, "Failed to update target address for client %s:%d", - inet_ntoa(e->client_addr.sin_addr), ntohs(e->client_addr.sin_port)); - } - } - return; - } - - if (r->tag < 0 || r->tag >= resolve_bindings_count) { - return; - } - client_entry_t *entry = resolve_bindings[r->tag]; - if (!entry || !entry->is_static) { - return; - } - if (entry->client_addr.sin_addr.s_addr == r->addr) { - return; - } - - struct sockaddr_in new_addr = entry->client_addr; - new_addr.sin_addr.s_addr = r->addr; - - client_entry_t *collision = NULL; - HASH_FIND(hh, conn_table, &new_addr, sizeof(new_addr), collision); - if (collision && collision != entry) { - log(LL_WARN, "Static binding '%s' re-resolved to %s:%d, but that address is already in use", - entry->bind_host, inet_ntoa(new_addr.sin_addr), ntohs(new_addr.sin_port)); - return; - } - - char old_ip[INET_ADDRSTRLEN], new_ip[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &entry->client_addr.sin_addr, old_ip, sizeof(old_ip)); - inet_ntop(AF_INET, &new_addr.sin_addr, new_ip, sizeof(new_ip)); - - HASH_DEL(conn_table, entry); - entry->client_addr.sin_addr.s_addr = r->addr; - HASH_ADD(hh, conn_table, client_addr, sizeof(entry->client_addr), entry); - log(LL_INFO, "Static binding '%s' address changed: %s -> %s", entry->bind_host, old_ip, new_ip); -} - -/** - * @brief Reads every queued re-resolve result. Each write is atomic, so one - * read of sizeof(resolve_result_t) is one message. - */ -static void drain_resolve_results(struct sockaddr_in *forward_addr) -{ - resolve_result_t r; - while (1) { - ssize_t n = read(resolve_result_rd, &r, sizeof(r)); - if (n < 0) { - if (errno == EINTR) { - continue; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) { - break; - } - serror_level(LL_WARN, "Failed to read hostname re-resolve result"); - break; - } - if (n == 0) { - break; - } - if (n != (ssize_t)sizeof(r)) { - log(LL_WARN, "Short read from hostname resolver (%zd bytes)", n); - break; - } - apply_resolve_result(&r, forward_addr); - } -} - -/** - * @brief Starts the resolver thread if there is at least one hostname to refresh. - */ -static void resolve_start_thread(void) -{ - int wake[2], result[2]; - pthread_t thread; - - if (!resolve_target_is_name && resolve_bindings_count == 0) { - return; - } - - if (pipe(wake) < 0 || pipe(result) < 0) { - log(LL_WARN, "Can't create pipes for hostname re-resolve, periodic refresh disabled"); - return; - } - resolve_wake_rd = wake[0]; - resolve_wake_wr = wake[1]; - resolve_result_rd = result[0]; - resolve_result_wr = result[1]; - - fcntl(resolve_wake_rd, F_SETFL, O_NONBLOCK); - fcntl(resolve_wake_wr, F_SETFL, O_NONBLOCK); - fcntl(resolve_result_rd, F_SETFL, O_NONBLOCK); - - if (pthread_create(&thread, NULL, resolve_thread_fn, NULL) != 0) { - log(LL_WARN, "Can't start hostname resolver thread, periodic refresh disabled"); - close(resolve_wake_rd); - close(resolve_wake_wr); - close(resolve_result_rd); - close(resolve_result_wr); - resolve_wake_rd = resolve_wake_wr = resolve_result_rd = resolve_result_wr = -1; - return; - } - pthread_detach(thread); - - if (resolve_interval_ms > 0) { - log(LL_INFO, "Re-resolving hostnames every %ld seconds (and on SIGHUP)", resolve_interval_ms / 1000); - } else { - log(LL_INFO, "Re-resolving hostnames on SIGHUP"); - } -} - -/** - * @brief Remembers the PID of a forked instance. - * - * Called by the configuration parser for every additional section, so that the - * parent process can forward signals to all the instances. - * - * @param pid PID of the forked instance. - */ -void register_child_instance(pid_t pid) -{ - pid_t *new_pids = realloc(child_pids, (child_pids_count + 1) * sizeof(pid_t)); - if (!new_pids) { - log(LL_WARN, "Out of memory, instance with PID %ld will not receive forwarded signals", (long)pid); - return; - } - child_pids = new_pids; - child_pids[child_pids_count++] = pid; -} - -#ifdef SIGHUP -/** - * @brief Handles SIGHUP: schedules reopening of the log file. - * - * The actual reopening is done by the main loop, the handler only sets a flag - * and forwards the signal to the instances of the other configuration sections. - * - * @param sig Signal number received by the process. - */ -static void sighup_handler(int sig) -{ - (void)sig; - log_reopen_pending = 1; - resolve_wake(); - // The list is filled before the handler is installed, so it can't change here - for (int i = 0; i < child_pids_count; i++) { - kill(child_pids[i], SIGHUP); - } -} -#endif - -/** - * @brief Creates a new client_entry_t structure and initializes it with the provided client and forward addresses. - * - * @param config Pointer to the obfuscator configuration structure. - * @param client_addr Pointer to a struct sockaddr_in representing the client's address. - * @param forward_addr Pointer to a struct sockaddr_in representing the address to which traffic should be forwarded. - * @return Pointer to the newly created client_entry_t structure, or NULL on failure. - */ -static client_entry_t * new_client_entry(obfuscator_config_t *config, struct sockaddr_in *client_addr, struct sockaddr_in *forward_addr) { - if (HASH_COUNT(conn_table) >= config->max_clients) { - log(LL_ERROR, "Maximum number of clients reached (%d), cannot add new client", config->max_clients); - return NULL; - } - client_entry_t * client_entry = malloc(sizeof(client_entry_t)); - if (!client_entry) { - log(LL_ERROR, "Failed to allocate memory for client entry"); - return NULL; - } - memset(client_entry, 0, sizeof(client_entry_t)); - // Set default version (latest) - client_entry->version = OBFUSCATION_VERSION; - // Set the client address - memcpy(&client_entry->client_addr, client_addr, sizeof(client_entry->client_addr)); - // Create a socket for the server connection - client_entry->server_sock = socket(AF_INET, SOCK_DGRAM, 0); - // TODO: add client address to log - if (client_entry->server_sock < 0) { - serror("Failed to create server socket for client"); - free(client_entry); - return NULL; - } -#ifdef __linux__ - // Set "Don't Fragment" flag - int optval = 1; - if (setsockopt(client_entry->server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &optval, sizeof(optval)) < 0) { - serror("Failed to set 'don't fragment' flag for client"); - close(client_entry->server_sock); - free(client_entry); - return NULL; - } - if (config->fwmark) { - if (setsockopt(client_entry->server_sock, SOL_SOCKET, SO_MARK, &config->fwmark, sizeof(config->fwmark)) < 0) { - log(LL_WARN, "Failed to set 'firewall mark' for client: %s", strerror(errno)); - } - } -#endif - // Set the server address to the specified one - connect(client_entry->server_sock, (struct sockaddr *)forward_addr, sizeof(*forward_addr)); - // Get the assigned port number - socklen_t our_addr_len = sizeof(client_entry->our_addr); - if (getsockname(client_entry->server_sock, (struct sockaddr *)&client_entry->our_addr, &our_addr_len) == -1) { - serror("Failed to get socket port number"); - close(client_entry->server_sock); - free(client_entry); - return NULL; - } - -#ifdef USE_EPOLL - struct epoll_event e = { - .events = EPOLLIN, - .data.ptr = client_entry - }; - if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_entry->server_sock, &e) != 0) { - serror("epoll_ctl for client socket"); - close(client_entry->server_sock); - free(client_entry); - return NULL; - } -#endif - - HASH_ADD(hh, conn_table, client_addr, sizeof(*client_addr), client_entry); - - log(LL_DEBUG, "Added binding: %s:%d:%d", - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), - ntohs(client_entry->our_addr.sin_port)); - - return client_entry; -} - -/** - * @brief Creates a new static client entry. - * - * This function allocates and initializes a new client_entry_t structure - * using the provided client and forward addresses, as well as the specified local port. - * - * @param config Pointer to the obfuscator configuration structure. - * @param client_addr Pointer to a sockaddr_in structure representing the client's address. - * @param forward_addr Pointer to a sockaddr_in structure representing the address to forward to. - * @param local_port The local port number to connect to the server. - * @param bind_host Original hostname from the configuration, stored if it is not an IPv4 literal. - * @return Pointer to the newly created client_entry_t structure, or NULL on failure. - */ -static client_entry_t * new_client_entry_static(obfuscator_config_t *config, struct sockaddr_in *client_addr, struct sockaddr_in *forward_addr, uint16_t local_port, const char *bind_host) { - if (HASH_COUNT(conn_table) >= config->max_clients) { - log(LL_ERROR, "Maximum number of clients reached (%d), cannot add new client", config->max_clients); - return NULL; - } - - // Check if such client already exists - client_entry_t *existing_entry; - HASH_FIND(hh, conn_table, client_addr, sizeof(*client_addr), existing_entry); - if (existing_entry) { - log(LL_ERROR, "Binding with client %s:%d already exists", - inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); - return NULL; - } - - client_entry_t * client_entry = malloc(sizeof(client_entry_t)); - if (!client_entry) { - log(LL_ERROR, "Failed to allocate memory for client entry"); - return NULL; - } - memset(client_entry, 0, sizeof(client_entry_t)); - // Set default version (latest) - client_entry->version = OBFUSCATION_VERSION; - // default masking type - client_entry->masking_handler = config->masking_handler; - // Set the client address - memcpy(&client_entry->client_addr, client_addr, sizeof(client_entry->client_addr)); - // Create a socket for the server connection - client_entry->server_sock = socket(AF_INET, SOCK_DGRAM, 0); - if (client_entry->server_sock < 0) { - serror("Failed to create server socket for client"); - free(client_entry); - return NULL; - } - // Bind the socket to the specified local port - client_entry->our_addr.sin_family = AF_INET; - // TODO: ability to bind to a specific address - client_entry->our_addr.sin_addr.s_addr = INADDR_ANY; - client_entry->our_addr.sin_port = htons(local_port); - // Set the local port number - if (bind(client_entry->server_sock, (struct sockaddr *)&client_entry->our_addr, sizeof(client_entry->our_addr)) < 0) { - serror("Failed to bind server socket to %s:%d", - inet_ntoa(client_entry->our_addr.sin_addr), local_port); - close(client_entry->server_sock); - free(client_entry); - return NULL; - } -#ifdef __linux__ - // Set "Don't Fragment" flag - int optval = 1; - if (setsockopt(client_entry->server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &optval, sizeof(optval)) < 0) { - serror("Failed to set 'don't fragment' flag for client %s:%d", - inet_ntoa(client_entry->client_addr.sin_addr), local_port); - close(client_entry->server_sock); - free(client_entry); - return NULL; - } - if (config->fwmark) { - if (setsockopt(client_entry->server_sock, SOL_SOCKET, SO_MARK, &config->fwmark, sizeof(config->fwmark)) < 0) { - log(LL_WARN, "Failed to set 'firewall mark' for client %s:%d: %s", - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), strerror(errno)); - } - } - -#endif - // Set the server address to the specified one - connect(client_entry->server_sock, (struct sockaddr *)forward_addr, sizeof(*forward_addr)); - -#ifdef USE_EPOLL - struct epoll_event e = { - .events = EPOLLIN, - .data.ptr = client_entry - }; - if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_entry->server_sock, &e) != 0) { - serror("epoll_ctl for client socket"); - close(client_entry->server_sock); - free(client_entry); - return NULL; - } -#endif - - client_entry->is_static = 1; - if (bind_host && !is_ipv4_literal(bind_host)) { - strncpy(client_entry->bind_host, bind_host, sizeof(client_entry->bind_host) - 1); - } - - HASH_ADD(hh, conn_table, client_addr, sizeof(*client_addr), client_entry); - - return client_entry; -} - -#ifndef USE_EPOLL -static client_entry_t *find_by_server_sock(int fd) { - client_entry_t *e, *tmp; - HASH_ITER(hh, conn_table, e, tmp) { - if (e->server_sock == fd) return e; - } - return NULL; -} -#endif - -/** - * @brief Prints the version information of the program. - * - * This function outputs the current version of the application to the standard output. - * Typically used to inform users about the build or release version. - */ -const char *version_string(void) { -#ifdef COMMIT -#ifndef ARCH - return "WireGuard Obfuscator (commit " COMMIT " @ " WG_OBFUSCATOR_GIT_REPO ")"; -#else - return "WireGuard Obfuscator (commit " COMMIT " @ " WG_OBFUSCATOR_GIT_REPO ") (" ARCH ")"; -#endif -#else -#ifndef ARCH - return "WireGuard Obfuscator v" WG_OBFUSCATOR_VERSION; -#else - return "WireGuard Obfuscator v" WG_OBFUSCATOR_VERSION " (" ARCH ")"; -#endif -#endif -} - -void print_version(void) { - fprintf(stderr, "Starting %s\n", version_string()); -} - -int main(int argc, char *argv[]) { - obfuscator_config_t config = {0}; - struct sockaddr_in - listen_addr, // Address for listening socket, for receiving data from the client - forward_addr; // Address for forwarding socket, for sending data to the server - uint8_t full_buffer[BUFFER_SIZE + PREBUFFER_SIZE]; - char target_host[256] = {0}; - int target_port = -1; - int key_length = 0; - in_addr_t s_listen_addr_client = INADDR_ANY; - long now, last_cleanup_time = 0; - struct addrinfo *addr; - int err; - struct addrinfo hints = { // for getaddrinfo - .ai_family = AF_INET, // IPv4 - .ai_socktype = SOCK_DGRAM, // UDP - }; - - print_version(); - - if (parse_config(argc, argv, &config) != 0) { - exit(EXIT_FAILURE); - } - - /* Start writing to the log file before validating the rest of the parameters, - so that configuration errors are logged there too */ - log_init(config.log_file_set ? config.log_file : NULL, config.log_timestamps); - -#ifdef USE_EPOLL - struct epoll_event events[MAX_EVENTS]; -#else - struct pollfd pollfds[config.max_clients + 2]; -#endif - - /* Check the parameters */ - // Check the listening port - if (!config.listen_port_set) { - log(LL_ERROR, "'source-lport' is not set in the configuration file"); - exit(EXIT_FAILURE); - } - - // Check the key - key_length = strlen(config.xor_key); - if (!config.xor_key_set || key_length == 0) { - log(LL_ERROR, "'key' is not set in the configuration file"); - exit(EXIT_FAILURE); - } - - // Check the target host and port - if (!config.forward_host_port_set) { - log(LL_ERROR, "'target' is not set"); - exit(EXIT_FAILURE); - } else { - char *port_delimiter = strchr(config.forward_host_port, ':'); - if (port_delimiter == NULL) { - log(LL_ERROR, "Invalid target host:port format: %s", config.forward_host_port); - exit(EXIT_FAILURE); - } - *port_delimiter = 0; - strncpy(target_host, config.forward_host_port, sizeof(target_host) - 1); - target_host[sizeof(target_host) - 1] = 0; // Ensure null-termination - target_port = atoi(port_delimiter + 1); - if (target_port <= 0) { - log(LL_ERROR, "Invalid target port: %s", port_delimiter + 1); - exit(EXIT_FAILURE); - } - } - - // 'allow-clean' is incompatible with static bindings: for a static binding - // there is no way to know in advance whether the client's traffic must be obfuscated - if (config.allow_clean && config.static_bindings_set) { - log(LL_ERROR, "'allow-clean' cannot be used together with 'static-bindings'"); - exit(EXIT_FAILURE); - } - - // Check the client interface - if (config.client_interface_set) { - s_listen_addr_client = inet_addr(config.client_interface); - if (s_listen_addr_client == INADDR_NONE) { - err = getaddrinfo(config.client_interface, NULL, &hints, &addr); - if (err != 0 || addr == NULL) { - log(LL_ERROR, "Invalid source interface '%s': %s", config.client_interface, gai_strerror(err)); - exit(EXIT_FAILURE); - } - s_listen_addr_client = ((struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr; - freeaddrinfo(addr); - } - } - - /* Set up signal handlers */ - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); -#ifdef SIGHUP - signal(SIGHUP, sighup_handler); -#endif - - /* Create listening socket */ - if ((listen_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - serror("Can't create source socket to listen"); - exit(EXIT_FAILURE); - } - -#ifdef __linux__ - /* Set "Don't Fragment" flag */ - int optval = 1; - if (setsockopt(listen_sock, IPPROTO_IP, IP_MTU_DISCOVER, &optval, sizeof(optval)) < 0) { - serror("Failed to set 'don't fragment' flag for listening socket"); - FAILURE(); - } - if (config.fwmark) { - if (setsockopt(listen_sock, SOL_SOCKET, SO_MARK, &config.fwmark, sizeof(config.fwmark)) < 0) { - log(LL_WARN, "Failed to set 'firewall mark' for listening socket: %s", strerror(errno)); - } - } -#endif - - /* Bind the listening socket to the specified address and port */ - memset(&listen_addr, 0, sizeof(listen_addr)); - listen_addr.sin_family = AF_INET; - listen_addr.sin_addr.s_addr = s_listen_addr_client; - listen_addr.sin_port = htons(config.listen_port); - if (bind(listen_sock, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) { - serror("Failed to bind source socket to %s:%d", - inet_ntoa(listen_addr.sin_addr), ntohs(listen_addr.sin_port)); - FAILURE(); - } - log(LL_INFO, "Listening on port %s:%d for source", inet_ntoa(listen_addr.sin_addr), ntohs(listen_addr.sin_port)); - - if (config.masking_handler_set) { - log(LL_INFO, "Using masking type: %s", config.masking_handler ? config.masking_handler->name : "none"); - } - - if (config.allow_clean) { - log(LL_INFO, "Non-obfuscated (clean) clients are allowed, their traffic will be forwarded as is"); - } - - /* Use epoll for events if enabled */ -#ifdef USE_EPOLL - epfd = epoll_create1(0); - if (epfd < 0) { - serror("epoll_create1"); - FAILURE(); - } - { - struct epoll_event ev = { - .events = EPOLLIN, - .data.fd = listen_sock - }; - if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev) != 0) { - serror("epoll_ctl for listen_sock"); - FAILURE(); - } - } -#endif - - /* Set up forward address */ - memset(&forward_addr, 0, sizeof(forward_addr)); - forward_addr.sin_family = AF_INET; - resolve_interval_ms = config.resolve_interval; - err = resolve_hostname_startup(target_host, &forward_addr.sin_addr, resolve_interval_ms, "target"); - if (err != 0) { - log(LL_ERROR, "Can't resolve hostname '%s': %s", target_host, gai_strerror(err)); - FAILURE(); - } - log(LL_DEBUG, "Resolved target hostname '%s' to %s", target_host, inet_ntoa(forward_addr.sin_addr)); - if (target_port > 65535) { - log(LL_ERROR, "Invalid target port: %d", target_port); - FAILURE(); - } - forward_addr.sin_port = htons(target_port); - log(LL_INFO, "Target: %s:%d", target_host, target_port); - strncpy(resolve_target_host, target_host, sizeof(resolve_target_host) - 1); - resolve_target_is_name = !is_ipv4_literal(target_host); - - /* Add static bindings if provided */ - if (config.static_bindings) { - // Parse static bindings - char *binding = strtok(config.static_bindings, ","); - while (binding) { - // Trim leading and trailing spaces - binding = trim(binding); - char *colon1 = strchr(binding, ':'); - if (!colon1) { - log(LL_ERROR, "Invalid static binding format: %s", binding); - exit(EXIT_FAILURE); - } - char *colon2 = strchr(colon1 + 1, ':'); - if (!colon2) { - log(LL_ERROR, "Invalid static binding format: %s", binding); - exit(EXIT_FAILURE); - } - *colon1 = 0; - *colon2 = 0; - - struct sockaddr_in client_addr = {0}; - client_addr.sin_family = AF_INET; - err = resolve_hostname_startup(binding, &client_addr.sin_addr, resolve_interval_ms, "static binding"); - if (err != 0) { - log(LL_ERROR, "Can't resolve hostname '%s' for static binding '%s:%s:%s': %s", - binding, binding, colon1 + 1, colon2 + 1, gai_strerror(err)); - FAILURE(); - } - log(LL_DEBUG, "Resolved static binding hostname '%s' to %s", binding, inet_ntoa(client_addr.sin_addr)); - int remote_port = atoi(colon1 + 1); - if (remote_port <= 0 || remote_port > 65535) { - log(LL_ERROR, "Invalid port '%s' for static binding '%s:%s:%s'", - colon1 + 1, binding, colon1 + 1, colon2 + 1); - FAILURE(); - } - int local_port = atoi(colon2 + 1); - if (local_port <= 0 || local_port > 65535) { - log(LL_ERROR, "Invalid port '%s' for static binding '%s:%s:%s'", - colon2 + 1, binding, colon1 + 1, colon2 + 1); - FAILURE(); - } - client_addr.sin_port = htons(remote_port); - - if (!new_client_entry_static(&config, &client_addr, &forward_addr, local_port, binding)) { - log(LL_ERROR, "Failed to create static binding: %s:%s:%s", - binding, colon1 + 1, colon2 + 1); - FAILURE(); - } - - log(LL_INFO, "Added static binding: %s:%d <-> %d:obfuscator:%d <-> %s:%d", - binding, remote_port, config.listen_port, - local_port, target_host, target_port); - - binding = strtok(NULL, ","); - } - free(config.static_bindings); - config.static_bindings = NULL; - } - - { - client_entry_t *e, *tmp; - int n = 0; - HASH_ITER(hh, conn_table, e, tmp) { - if (e->is_static && e->bind_host[0]) { - n++; - } - } - if (n > 0) { - resolve_bindings = calloc((size_t)n, sizeof(*resolve_bindings)); - if (!resolve_bindings) { - log(LL_WARN, "Out of memory, static binding hostnames will not be re-resolved"); - } else { - HASH_ITER(hh, conn_table, e, tmp) { - if (e->is_static && e->bind_host[0]) { - resolve_bindings[resolve_bindings_count++] = e; - } - } - } - } - resolve_start_thread(); -#ifdef USE_EPOLL - if (resolve_result_rd >= 0) { - struct epoll_event ev = { - .events = EPOLLIN, - .data.fd = resolve_result_rd - }; - if (epoll_ctl(epfd, EPOLL_CTL_ADD, resolve_result_rd, &ev) != 0) { - serror("epoll_ctl for hostname resolver"); - FAILURE(); - } - } -#endif - } - - log(LL_INFO, "WireGuard obfuscator successfully started"); - - /* Main loop */ - while (1) { - // Reopen the log file after it has been rotated, requested by SIGHUP - if (log_reopen_pending) { - log_reopen_pending = 0; - log_reopen(); - } - - // Using epoll or poll to wait for events -#ifdef USE_EPOLL - int events_n = epoll_wait(epfd, events, MAX_EVENTS, POLL_TIMEOUT); - if (events_n < 0) { - if (errno == EINTR) { - // Interrupted by a signal, e.g. SIGHUP - continue; - } - serror("epoll_wait"); - FAILURE(); - } -#else - int nfds = 0; - pollfds[nfds].fd = listen_sock; - pollfds[nfds].events = POLLIN; - nfds++; - if (resolve_result_rd >= 0) { - pollfds[nfds].fd = resolve_result_rd; - pollfds[nfds].events = POLLIN; - nfds++; - } - client_entry_t *entry, *tmp_entry; - HASH_ITER(hh, conn_table, entry, tmp_entry) { - if (nfds >= config.max_clients + 2) { - log(LL_DEBUG, "Too many clients, cannot add more"); - break; - } - pollfds[nfds].fd = entry->server_sock; - pollfds[nfds].events = POLLIN; - nfds++; - } - int ret = poll(pollfds, nfds, POLL_TIMEOUT); - if (ret < 0) { - if (errno == EINTR) { - // Interrupted by a signal, e.g. SIGHUP - continue; - } - serror("poll"); - FAILURE(); - } -#endif - - // Get the current time - struct timespec now_ts; - clock_gettime(CLOCK_MONOTONIC, &now_ts); - now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; - -#ifdef USE_EPOLL - for (int e = 0; e < events_n; e++) { - struct epoll_event *event = &events[e]; - if (resolve_result_rd >= 0 && event->data.fd == resolve_result_rd) { - drain_resolve_results(&forward_addr); - continue; - } - if (event->data.fd == listen_sock) { -#else - for (int e = 0; e < nfds; e++) if (pollfds[e].revents & POLLIN) { - if (resolve_result_rd >= 0 && pollfds[e].fd == resolve_result_rd) { - drain_resolve_results(&forward_addr); - continue; - } - if (pollfds[e].fd == listen_sock) { -#endif - /* *** Handle incoming data from the clients *** */ - uint8_t *buffer = full_buffer + PREBUFFER_SIZE; - struct sockaddr_in sender_addr = {0}; - socklen_t sender_addr_len = sizeof(sender_addr); - int length = recvfrom(listen_sock, buffer, BUFFER_SIZE, MSG_TRUNC | MSG_DONTWAIT, (struct sockaddr *)&sender_addr, &sender_addr_len); - if (length < 0) { - // A readiness notification does not guarantee that there is something - // to read by the time we get here, so an empty socket is not an error - if (errno != EAGAIN && errno != EWOULDBLOCK) { - serror_level(LL_DEBUG, "recvfrom client"); - } - continue; - } - if (length > BUFFER_SIZE) { - log(LL_DEBUG, "Received packet from %s:%d is too large (%d bytes), while buffer size is %d bytes, ignoring", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), length, BUFFER_SIZE); - continue; - } - - // Find the client entry if any - client_entry_t *client_entry; - HASH_FIND(hh, conn_table, &sender_addr, sizeof(sender_addr), client_entry); - - uint8_t obfuscated = length >= 4 && is_obfuscated(buffer); - // Is it masked packet maybe? - masking_handler_t *masking_handler = config.masking_handler; - if (obfuscated) { - length = masking_unwrap_from_client(&buffer, length, &config, client_entry, listen_sock, &sender_addr, &forward_addr, &masking_handler); - if (length <= 0) { - // Nothing to do - continue; - } - } - // Check the length - if (length < 4) { - log(LL_DEBUG, "Received too short packet from %s:%d (%d bytes), ignoring", inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), length); - continue; - } - - uint8_t version = client_entry ? client_entry->version : OBFUSCATION_VERSION; - - if (verbose >= LL_TRACE) { - log(LL_TRACE, "Received %d bytes from %s:%d to %s:%d (known=%s, obfuscated=%s)", - length, - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), - target_host, target_port, - client_entry ? "yes" : "no", obfuscated ? "yes" : "no"); - log_hexdump(LL_TRACE, obfuscated ? "X->: " : "O->: ", buffer, length); - } - - if (obfuscated) { - // decode - int original_length = length; - length = decode(buffer, length, config.xor_key, key_length, &version); - if (length < 4 || length > original_length) { - log(LL_DEBUG, "Failed to decode packet from %s:%d (original_length=%d, decoded_length=%d)", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), original_length, length); - continue; - } - } - - // Is it handshake? - if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE) { - log(LL_DEBUG, "Received WireGuard handshake from %s:%d to %s:%d (%d bytes, obfuscated=%s)", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), - target_host, target_port, - length, - obfuscated ? "yes" : "no"); - - if (!client_entry) { - client_entry = new_client_entry(&config, &sender_addr, &forward_addr); - if (!client_entry) { - continue; - } - client_entry->last_activity_time = now; - client_entry->last_incoming_time = 0; - client_entry->masking_handler = masking_handler; - } - if (config.allow_clean) { - // Remember whether this client speaks plain WireGuard, - // its traffic will be forwarded as is in both directions - if (!obfuscated && !client_entry->client_clean) { - log(LL_INFO, "Client %s:%d is not obfuscated, forwarding its traffic as is", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port)); - } - client_entry->client_clean = !obfuscated; - if (client_entry->client_clean) { - // No masking for clean clients - client_entry->masking_handler = NULL; - } - } - if (!obfuscated && !client_entry->client_clean) { - masking_on_handshake_req_from_client(&config, client_entry, listen_sock, &sender_addr, &forward_addr); - } - client_entry->handshake_direction = DIR_CLIENT_TO_SERVER; - client_entry->last_handshake_request_time = now; - } - // Is it handshake response? - else if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE_RESP) { - if (!client_entry) { - log(LL_DEBUG, "Received WireGuard handshake response from %s:%d, but no connection entry found for this client", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port)); - continue; - } - - log(LL_DEBUG, "Received WireGuard handshake response from %s:%d to %s:%d (%d bytes, obfuscated=%s)", - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), - target_host, target_port, - length, obfuscated ? "yes" : "no"); - - // Check handshake timeout - if (now - client_entry->last_handshake_request_time > HANDSHAKE_TIMEOUT) { - log(LL_DEBUG, "Ignoring WireGuard handshake response, handshake timeout"); - continue; - } - - if (client_entry->handshake_direction != DIR_SERVER_TO_CLIENT) { - log(LL_DEBUG, "Received handshake response from %s:%d to %s:%d, but the handshake direction is not set to server-to-client", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), - target_host, target_port); - continue;; - } - - log(!client_entry->handshaked ? LL_INFO : LL_DEBUG, "Handshake established with %s:%d to %s:%d (reverse)", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), - target_host, target_port); - client_entry->handshaked = 1; - client_entry->client_obfuscated = obfuscated; - client_entry->server_obfuscated = !obfuscated; - client_entry->last_handshake_time = now; - } - // If it's not a handshake or handshake response, connection is not established yet - else if (!client_entry || !client_entry->handshaked) { - log(LL_DEBUG, "Ignoring data (packet type #%u) from %s:%d to %s:%d until the handshake is completed", - WG_TYPE(buffer), - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), - target_host, target_port); - continue; - } - - // Version downgrade check - if (version < client_entry->version) { - log(LL_WARN, "Client %s:%d uses old obfuscation version, downgrading from %d to %d", inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), - client_entry->version, version); - client_entry->version = version; - } - - if (!obfuscated && !client_entry->client_clean) { - // If the packet is not obfuscated, we need to encode it - length = encode(buffer, length, config.xor_key, key_length, client_entry->version, config.max_dummy_length_data); - if (length < 4) { - log(LL_ERROR, "Failed to encode packet from %s:%d (too short, length=%d)", - inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port), length); - continue; - } - length = masking_data_wrap_to_server(&buffer, length, &config, client_entry, listen_sock, &forward_addr); - } - - log_hexdump(LL_TRACE, (!obfuscated && !client_entry->client_clean) ? "X->: " : "O->: ", buffer, length); - - length = send(client_entry->server_sock, buffer, length, 0); - if (length < 0) { - serror_level(LL_DEBUG, "sendto %s:%d", target_host, target_port); - continue; - } - client_entry->last_activity_time = now; - } else { // if (event->data.fd == listen_sock) - /* *** Handle data from the server *** */ -#ifdef USE_EPOLL - client_entry_t *client_entry = event->data.ptr; -#else - client_entry_t *client_entry = find_by_server_sock(pollfds[e].fd); -#endif - uint8_t *buffer = full_buffer + PREBUFFER_SIZE; - int length = recv(client_entry->server_sock, buffer, BUFFER_SIZE, MSG_TRUNC | MSG_DONTWAIT); - if (length < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - serror_level(LL_DEBUG, "recv from server"); - } - continue; - } - if (length > BUFFER_SIZE) { - log(LL_DEBUG, "Received packet from %s:%d is too large (%d bytes), while buffer size is %d bytes, ignoring", - target_host, target_port, length, BUFFER_SIZE); - continue; - } - uint8_t obfuscated = length >= 4 && is_obfuscated(buffer); - if (obfuscated) { - // Is it masked packet maybe? - length = masking_unwrap_from_server(&buffer, length, &config, client_entry, listen_sock, &forward_addr); - if (length <= 0) { - // Nothing to do - continue; - } - } - // Check the length - if (length < 4) { - log(LL_DEBUG, "Received too short packet from %s:%d (%d bytes), ignoring", target_host, target_port, length); - continue; - } - - uint8_t version = client_entry->version; - - if (verbose >= LL_TRACE) { - log(LL_TRACE, "Received %d bytes from %s:%d to %s:%d (obfuscated=%s)", - length, - target_host, target_port, - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), - obfuscated ? "yes" : "no"); - log_hexdump(LL_TRACE, obfuscated ? "<-X: " : "<-O: ", buffer, length); - } - - if (obfuscated) { - // decode - int original_length = length; - length = decode(buffer, length, config.xor_key, key_length, &version); - if (length < 4 || length > original_length) { - log(LL_DEBUG, "Failed to decode packet from %s:%d (original_length=%d, decoded_length=%d)", target_host, target_port, original_length, length); - continue; - } - } - - // Is it handshake? - if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE) { - log(LL_DEBUG, "Received WireGuard handshake from %s:%d to %s:%d (%d bytes, obfuscated=%s)", - target_host, target_port, - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), - length, - obfuscated ? "yes" : "no"); - if (!obfuscated && !client_entry->client_clean) { - // Send STUN binding request before the obfuscated handshake - masking_on_handshake_req_from_server(&config, client_entry, listen_sock, &client_entry->client_addr, &forward_addr); - } - client_entry->handshake_direction = DIR_SERVER_TO_CLIENT; - client_entry->last_handshake_request_time = now; - } - // Is it handshake response? - else if (WG_TYPE(buffer) == WG_TYPE_HANDSHAKE_RESP) { - log(LL_DEBUG, "Received WireGuard handshake response from %s:%d to %s:%d (%d bytes, obfuscated=%s)", - target_host, target_port, - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), - length, obfuscated ? "yes" : "no"); - - // Check handshake timeout - if (now - client_entry->last_handshake_request_time > HANDSHAKE_TIMEOUT) { - log(LL_DEBUG, "Ignoring WireGuard handshake response, handshake timeout"); - continue; - } - - if (client_entry->handshake_direction != DIR_CLIENT_TO_SERVER) { - log(LL_DEBUG, "Received handshake response from %s:%d to %s:%d, but the handshake direction is not set to client-to-server", - target_host, target_port, - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port)); - continue; - } - - log(!client_entry->handshaked ? LL_INFO : LL_DEBUG, "Handshake established with %s:%d to %s:%d (direct)", - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), - target_host, target_port); - if (!client_entry->handshaked && client_entry->masking_handler && !config.masking_handler_set) { - log(LL_INFO, "Autodetected masking handler for client %s:%d: %s", inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port), client_entry->masking_handler->name); - } - client_entry->handshaked = 1; - client_entry->client_obfuscated = !obfuscated && !client_entry->client_clean; - client_entry->server_obfuscated = obfuscated; - client_entry->last_handshake_time = now; - } - // If it's not a handshake or handshake response, connection is not established yet - else if (!client_entry->handshaked) { - log(LL_DEBUG, "Ignoring response (packet type #%u) from %s:%d to %s:%d until the handshake is completed", - WG_TYPE(buffer), - target_host, target_port, - inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port)); - continue; - } - - // Version downgrade check - if (version < client_entry->version) { - log(LL_WARN, "Server %s:%d uses old obfuscation version, downgrading from %d to %d", - target_host, target_port, client_entry->version, version); - client_entry->version = version; - } - - if (!obfuscated && !client_entry->client_clean) { - // If the packet is not obfuscated, we need to encode it - length = encode(buffer, length, config.xor_key, key_length, client_entry->version, config.max_dummy_length_data); - if (length < 4) { - log(LL_ERROR, "Failed to encode packet from %s:%d", target_host, target_port); - continue; - } - length = masking_data_wrap_to_client(&buffer, length, &config, client_entry, listen_sock, &forward_addr); - } - - log_hexdump(LL_TRACE, (!obfuscated && !client_entry->client_clean) ? "<-X: " : "<-O: ", buffer, length); - - // Send the response back to the original client - length = sendto(listen_sock, buffer, length, 0, (struct sockaddr *)&client_entry->client_addr, sizeof(client_entry->client_addr)); - if (length < 0) { - serror_level(LL_DEBUG, "sendto %s:%d", inet_ntoa(client_entry->client_addr.sin_addr), ntohs(client_entry->client_addr.sin_port)); - continue; - } - client_entry->last_activity_time = now; - client_entry->last_incoming_time = now; - } // if (event->data.fd != listen_sock) - } // for (int e = 0; e < events_n; e++) - - if (now - last_cleanup_time >= ITERATE_INTERVAL) { - client_entry_t *current_entry, *tmp_current_entry; - // Iterate over all client entries - HASH_ITER(hh, conn_table, current_entry, tmp_current_entry) { - // Check if the entry is idle for too long - uint8_t idle = now - current_entry->last_activity_time >= config.idle_timeout; - uint8_t incoming_timeout = config.in_timeout > 0 && now - current_entry->last_incoming_time >= config.in_timeout; - uint8_t handshake_timeout = !current_entry->handshaked && now - current_entry->last_activity_time >= HANDSHAKE_TIMEOUT; - if ((idle || incoming_timeout || handshake_timeout) && !current_entry->is_static) { // Do not remove static entries - // Remove old entry - if (idle) { - log(LL_INFO, "Removing idle client %s:%d", inet_ntoa(current_entry->client_addr.sin_addr), ntohs(current_entry->client_addr.sin_port)); - } else if (incoming_timeout) { - log(LL_INFO, "Removing client %s:%d due to incoming timeout", inet_ntoa(current_entry->client_addr.sin_addr), ntohs(current_entry->client_addr.sin_port)); - } else if (handshake_timeout) { - log(LL_DEBUG, "Removing client %s:%d due to handshake timeout", inet_ntoa(current_entry->client_addr.sin_addr), ntohs(current_entry->client_addr.sin_port)); - } -#ifdef USE_EPOLL - epoll_ctl(epfd, EPOLL_CTL_DEL, current_entry->server_sock, NULL); -#endif - close(current_entry->server_sock); - HASH_DEL(conn_table, current_entry); - free(current_entry); - continue; - } - - // Check if we need to call masking timer - if (current_entry->masking_handler && current_entry->masking_handler->timer_interval_s > 0 - && now - current_entry->last_masking_timer_time >= current_entry->masking_handler->timer_interval_s * 1000) { - current_entry->last_masking_timer_time = now; - masking_on_timer(&config, current_entry, listen_sock, &forward_addr); - } - } - // Update the last cleanup time - last_cleanup_time = now; - } - } // while (1) - - // You should never reach this point, but just in case - return 0; -} diff --git a/ios/Tunnel/obfuscator/wg-obfuscator.h b/ios/Tunnel/obfuscator/wg-obfuscator.h deleted file mode 100644 index ca9c7a6..0000000 --- a/ios/Tunnel/obfuscator/wg-obfuscator.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef _WG_OBFUSCATOR_H_ -#define _WG_OBFUSCATOR_H_ - -#include -#include -#include -#include -#include "uthash.h" - -// on Linux, use epoll for better performance -#ifdef __linux__ -#define USE_EPOLL -#endif - -#ifdef USE_EPOLL -#include -#define MAX_EVENTS 1024 -#else -#include -#endif - -#define WG_OBFUSCATOR_VERSION "1.6" -#define WG_OBFUSCATOR_GIT_REPO "https://github.com/ClusterM/wg-obfuscator" - -#define LL_DEFAULT LL_INFO // default logging level - -// Main parameters -// TODO: make these configurable via command line arguments or config file -#define BUFFER_SIZE 65535 // size of the buffer for receiving data from the clients and server -#define PREBUFFER_SIZE 1024 // size of the additional buffer size before the main buffer -#define POLL_TIMEOUT 5000 // in milliseconds -#define HANDSHAKE_TIMEOUT 5000 // in milliseconds -#define ITERATE_INTERVAL 1000 // in milliseconds -#define MAX_DUMMY_LENGTH_TOTAL 1024 // maximum length of a packet after dummy data extension -#define MAX_DUMMY_LENGTH_HANDSHAKE 512 // maximum length of dummy data for handshake packets - -#define MAX_CLIENTS_DEFAULT 1024 // maximum number of clients -#define IDLE_TIMEOUT_DEFAULT 300000 // in milliseconds -#define IN_TIMEOUT_DEFAULT 0 // in milliseconds -#define MAX_DUMMY_LENGTH_DATA_DEFAULT 4 // maximum length of dummy data for data packets - -// Default instance name -#define DEFAULT_INSTANCE_NAME "main" - -// Logging levels -#define LL_ERROR 0 -#define LL_WARN 1 -#define LL_INFO 2 -#define LL_DEBUG 3 -#define LL_TRACE 4 - -// Maximum length of a single log line, longer hex dumps are split into several lines -#define LOG_LINE_MAX 16384 - -#define log(level, fmt, ...) do { if (verbose >= (level)) log_printf((level), fmt, ##__VA_ARGS__); } while (0) -#define serror_level(level, fmt, ...) log(level, fmt " - %s (%d)", ##__VA_ARGS__, strerror(errno), errno) -#define serror(fmt, ...) serror_level(LL_ERROR, fmt, ##__VA_ARGS__) - -// Direction of... something -typedef enum { - DIR_CLIENT_TO_SERVER = 0, - DIR_SERVER_TO_CLIENT = 1, -} direction_t; - -struct masking_handler; // forward declaration -typedef struct masking_handler masking_handler_t; - -// Structure to hold obfuscator configuration -typedef struct { - int listen_port; // Listening port for the obfuscator - char forward_host_port[256]; // Host and port to forward the data to - char xor_key[256]; // Key for obfuscation - char client_interface[256]; // Client interface as a string - char *static_bindings; // Static bindings as a string - int max_clients; // Maximum number of clients - long idle_timeout; // Idle timeout in milliseconds - long in_timeout; // Incoming timeout in milliseconds - int max_dummy_length_data; // Maximum length of dummy data for data packets - uint32_t fwmark; // Firewall mark - masking_handler_t *masking_handler; // Masking handler to use - uint8_t allow_clean; // 1 if non-obfuscated (clean) clients are allowed, 0 otherwise - char log_file[512]; // Path of the log file - int8_t log_timestamps; // 1 to force timestamps on, 0 to force them off, -1 for auto - long resolve_interval; // Hostname re-resolve interval in milliseconds, 0 to disable periodic refresh - - uint8_t log_file_set; // 1 if the log file is set, 0 otherwise - uint8_t listen_port_set; // 1 if the listen port is set, 0 otherwise - uint8_t forward_host_port_set; // 1 if the forward host and port are set, 0 otherwise - uint8_t xor_key_set; // 1 if the XOR key is set, 0 otherwise - uint8_t client_interface_set; // 1 if the client interface is set, 0 otherwise - uint8_t static_bindings_set; // 1 if the static bindings are set, 0 otherwise - uint8_t masking_handler_set; // 1 if the masking handler is set, 0 otherwise -} obfuscator_config_t; - -// Structure to hold client connection information -typedef struct { - struct sockaddr_in client_addr; // client address and port (key for the hash table) - struct sockaddr_in our_addr; // our address and port on the server connection - long last_activity_time; // last time we received data from/to this client - long last_incoming_time; // last time we received data from the remote server - long last_handshake_request_time; // last time we received a handshake request from/to this client - long last_handshake_time; // last time we received a handshake response from/to this client - long last_masking_timer_time; // last time we called the masking timer handler for this client - int server_sock; // socket for the connection to the server - uint8_t version; // obfuscation version - masking_handler_t *masking_handler; // masking handler in use - uint8_t handshaked : 1; // 1 if the handshake is complete, 0 otherwise - uint8_t handshake_direction : 1; // 1 if the handshake is from client to server, 0 if from server to client - uint8_t client_obfuscated : 1; // 1 if the client is obfuscated, 0 otherwise - uint8_t server_obfuscated : 1; // 1 if the server is obfuscated, 0 otherwise - uint8_t client_clean : 1; // 1 if the client speaks plain (non-obfuscated) WireGuard, traffic is passed through as is (allow-clean mode) - uint8_t is_static : 1; // 1 if this is a static binding entry, 0 otherwise - char bind_host[256]; // Original hostname of a static binding, empty if the address is a literal or the entry is dynamic - UT_hash_handle hh; -} client_entry_t; - -// Verbosity level -extern int verbose; -// Section name (for multiple instances) -extern char section_name[256]; - -const char *version_string(void); -void print_version(void); -void register_child_instance(pid_t pid); - -void log_init(const char *path, int8_t timestamps_mode); -void log_reopen(void); -#ifdef __GNUC__ -void log_printf(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3))); -#else -void log_printf(int level, const char *fmt, ...); -#endif -void log_hexdump(int level, const char *prefix, const uint8_t *data, int length); - -#endif diff --git a/ios/Tunnel/pcarelay.c b/ios/Tunnel/pcarelay.c deleted file mode 100644 index b3ccdd1..0000000 --- a/ios/Tunnel/pcarelay.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * pcarelay — in-process transport switch for the iOS packet tunnel. - * wg-obfuscator (one instance, iOS forbids fork) sends to 127.0.0.1:; - * we forward either over UDP to the server's obfuscator port or over TCP to - * the server's udptcp (frames: 2-byte length + packet). Switch with pcarelay_use_tcp(). - * Mode 2 ("Облако"): packets go to a Swift callback (WebSocket to the user's Keenetic), answers come back via pcarelay_ws_in(). - */ -#include "pcarelay.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static atomic_int use_tcp = 0; /* 0 udp, 1 tcp, 2 ws */ -static pcarelay_ws_out_fn ws_out; -static int g_local = -1; -static pthread_mutex_t peer_mu = PTHREAD_MUTEX_INITIALIZER; -static struct sockaddr_storage g_peer; -static socklen_t g_pl; -static atomic_ullong rx_bytes = 0; -static char g_host[256], g_udp_port[8], g_tcp_port[8]; -static int g_local_port; - -static int dial(int socktype, const char *port) { - struct addrinfo hints = {0}, *res, *r; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = socktype; - if (getaddrinfo(g_host, port, &hints, &res) != 0) return -1; - int s = -1; - for (r = res; r; r = r->ai_next) { - s = socket(r->ai_family, r->ai_socktype, 0); - if (s < 0) continue; - if (connect(s, r->ai_addr, r->ai_addrlen) == 0) break; - close(s); s = -1; - } - freeaddrinfo(res); - if (s >= 0 && socktype == SOCK_STREAM) { - int one = 1; - setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one); - setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); - setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one); - } - return s; -} - -static int read_full(int fd, unsigned char *b, size_t n) { - size_t got = 0; - while (got < n) { ssize_t r = read(fd, b + got, n - got); if (r <= 0) return -1; got += (size_t)r; } - return 0; -} - -static void *loop(void *arg) { - (void)arg; - struct sockaddr_in la = {0}; - la.sin_family = AF_INET; - la.sin_port = htons((uint16_t)g_local_port); - la.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - int local = socket(AF_INET, SOCK_DGRAM, 0); - if (local < 0 || bind(local, (struct sockaddr *)&la, sizeof la) < 0) return NULL; - g_local = local; - int udp = -1, tcp = -1; - struct sockaddr_storage peer; socklen_t pl = 0; - unsigned char buf[65537]; - time_t last_dial = 0; - for (;;) { - int mode = atomic_load(&use_tcp), want_tcp = mode == 1; - if (mode == 2) { if (udp >= 0) { close(udp); udp = -1; } if (tcp >= 0) { close(tcp); tcp = -1; } } - if (!want_tcp && tcp >= 0) { close(tcp); tcp = -1; } - if (want_tcp && udp >= 0) { close(udp); udp = -1; } - if (mode == 0 && udp < 0 && time(NULL) != last_dial) { last_dial = time(NULL); udp = dial(SOCK_DGRAM, g_udp_port); } - int up = want_tcp ? tcp : udp; - struct pollfd p[2] = {{local, POLLIN, 0}, {up, POLLIN, 0}}; - poll(p, up >= 0 ? 2 : 1, 1000); - if (p[0].revents & POLLIN) { - pl = sizeof peer; - ssize_t n = recvfrom(local, buf, 65535, 0, (struct sockaddr *)&peer, &pl); - if (n <= 0) continue; - pthread_mutex_lock(&peer_mu); g_peer = peer; g_pl = pl; pthread_mutex_unlock(&peer_mu); - if (mode == 2) { - if (ws_out) ws_out(buf, (int)n); - } else if (want_tcp) { - if (tcp < 0 && time(NULL) != last_dial) { last_dial = time(NULL); tcp = dial(SOCK_STREAM, g_tcp_port); } - if (tcp >= 0) { - unsigned char h[2] = {(unsigned char)(n >> 8), (unsigned char)n}; - if (send(tcp, h, 2, 0) != 2 || send(tcp, buf, (size_t)n, 0) != n) { close(tcp); tcp = -1; } - } - } else if (udp >= 0) { - send(udp, buf, (size_t)n, 0); - } - } - if (up >= 0 && (p[1].revents & (POLLIN | POLLHUP | POLLERR))) { - ssize_t n; - if (want_tcp) { - unsigned char h[2]; - if (read_full(tcp, h, 2) < 0) { close(tcp); tcp = -1; continue; } - n = (h[0] << 8) | h[1]; - if (n == 0 || read_full(tcp, buf, (size_t)n) < 0) { close(tcp); tcp = -1; continue; } - } else { - n = recv(udp, buf, 65535, 0); - if (n <= 0) { close(udp); udp = -1; continue; } - } - atomic_fetch_add(&rx_bytes, (unsigned long long)n); - if (pl) sendto(local, buf, (size_t)n, 0, (struct sockaddr *)&peer, pl); - } - } -} - -int pcarelay_start(int local_port, const char *host, int udp_port, int tcp_port) { - g_local_port = local_port; - snprintf(g_host, sizeof g_host, "%s", host); - snprintf(g_udp_port, sizeof g_udp_port, "%d", udp_port); - snprintf(g_tcp_port, sizeof g_tcp_port, "%d", tcp_port); - pthread_t t; - if (pthread_create(&t, NULL, loop, NULL) != 0) return -1; - pthread_detach(t); - return 0; -} - -void pcarelay_use_tcp(int on) { atomic_store(&use_tcp, on ? 1 : 0); } -int pcarelay_is_tcp(void) { return atomic_load(&use_tcp) == 1; } - -void pcarelay_use_ws(pcarelay_ws_out_fn out) { ws_out = out; atomic_store(&use_tcp, 2); } -int pcarelay_is_ws(void) { return atomic_load(&use_tcp) == 2; } - -void pcarelay_ws_in(const unsigned char *p, int n) { - if (g_local < 0 || n <= 0) return; - atomic_fetch_add(&rx_bytes, (unsigned long long)n); - pthread_mutex_lock(&peer_mu); - struct sockaddr_storage peer = g_peer; socklen_t pl = g_pl; - pthread_mutex_unlock(&peer_mu); - if (pl) sendto(g_local, p, (size_t)n, 0, (struct sockaddr *)&peer, pl); -} -unsigned long long pcarelay_rx_bytes(void) { return atomic_load(&rx_bytes); } diff --git a/ios/Tunnel/pcarelay.h b/ios/Tunnel/pcarelay.h deleted file mode 100644 index 846988b..0000000 --- a/ios/Tunnel/pcarelay.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef PCARELAY_H -#define PCARELAY_H - -/* Start the transport switch thread: obfuscator -> 127.0.0.1:local_port -> host via UDP or TCP. */ -int pcarelay_start(int local_port, const char *host, int udp_port, int tcp_port); -void pcarelay_use_tcp(int on); -int pcarelay_is_tcp(void); -/* Bytes received back from the server on any path (auto-detects a dead UDP). */ -unsigned long long pcarelay_rx_bytes(void); - -/* Mode "Облако": each packet from the obfuscator goes to out() (Swift sends it over WebSocket); - packets from the WebSocket are handed back with pcarelay_ws_in(). */ -typedef void (*pcarelay_ws_out_fn)(const unsigned char *p, int n); -void pcarelay_use_ws(pcarelay_ws_out_fn out); -int pcarelay_is_ws(void); -void pcarelay_ws_in(const unsigned char *p, int n); - -/* wg-obfuscator compiled in with -Dmain=wgobf_main (one instance per process). */ -int wgobf_main(int argc, char **argv); - -#endif diff --git a/ios/Tunnel/ru_bypass.txt b/ios/Tunnel/ru_bypass.txt deleted file mode 100644 index 5945a3b..0000000 --- a/ios/Tunnel/ru_bypass.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Russian IPv4 networks (RIPE delegated, all sizes, 2026-10-30) -> excludedRoutes (go direct) -2.56.24.0/22, 2.56.88.0/22, 2.56.180.0/22, 2.56.240.0/23, 2.57.0.0/24, 2.57.36.0/22, 2.57.52.0/22, 2.57.80.0/22, 2.57.112.0/22, 2.57.184.0/22, 2.58.68.0/22, 2.58.98.0/24, 2.58.124.0/22, 2.58.212.0/24, 2.59.51.0/24, 2.59.76.0/22, 2.59.80.0/22, 2.59.160.0/22, 2.59.216.0/22, 2.59.240.0/22, 2.60.0.0/14, 2.92.0.0/14, 5.1.41.0/24, 5.1.48.0/21, 5.2.32.0/19, 5.3.0.0/16, 5.8.0.0/20, 5.8.16.0/21, 5.8.28.0/22, 5.8.36.0/22, 5.8.42.0/23, 5.8.44.0/22, 5.8.48.0/20, 5.8.64.0/22, 5.8.69.0/24, 5.8.72.0/21, 5.8.80.0/21, 5.8.88.0/22, 5.8.160.0/20, 5.8.176.0/21, 5.8.192.0/19, 5.8.224.0/20, 5.11.60.0/22, 5.11.64.0/20, 5.16.0.0/14, 5.23.48.0/21, 5.23.96.0/20, 5.28.16.0/20, 5.32.144.0/21, 5.35.0.0/19, 5.35.40.0/22, 5.35.48.0/20, 5.35.64.0/22, 5.35.72.0/21, 5.35.80.0/20, 5.35.96.0/21, 5.35.112.0/20, 5.35.128.0/19, 5.39.160.0/21, 5.42.0.0/17, 5.42.215.0/24, 5.43.128.0/19, 5.43.240.0/21, 5.44.0.0/20, 5.44.40.0/22, 5.44.45.0/24, 5.44.46.0/23, 5.44.48.0/20, 5.44.168.0/21, 5.45.192.0/18, 5.53.16.0/20, 5.53.120.0/21, 5.56.136.0/21, 5.57.212.0/24, 5.57.216.0/21, 5.57.232.0/21, 5.61.8.0/21, 5.61.16.0/21, 5.61.232.0/21, 5.63.152.0/21, 5.77.0.0/19, 5.79.128.0/17, 5.100.64.0/18, 5.100.244.0/22, 5.101.0.0/19, 5.101.32.0/20, 5.101.48.0/22, 5.101.60.0/22, 5.101.64.0/19, 5.101.128.0/21, 5.101.152.0/21, 5.101.192.0/20, 5.101.208.0/21, 5.101.218.0/24, 5.101.224.0/19, 5.102.152.0/21, 5.104.32.0/19, 5.104.200.0/21, 5.128.0.0/14, 5.133.76.0/22, 5.134.216.0/21, 5.136.0.0/13, 5.144.64.0/20, 5.144.96.0/19, 5.145.160.0/21, 5.145.192.0/18, 5.149.144.0/20, 5.149.200.0/21, 5.149.208.0/20, 5.153.128.0/18, 5.158.96.0/19, 5.158.232.0/21, 5.159.96.0/20, 5.159.112.0/21, 5.164.0.0/14, 5.172.0.0/19, 5.172.178.0/24, 5.175.88.0/21, 5.175.96.0/19, 5.178.24.0/21, 5.178.80.0/21, 5.180.92.0/22, 5.180.101.0/24, 5.180.174.0/23, 5.180.240.0/22, 5.181.0.0/22, 5.181.12.0/24, 5.181.60.0/22, 5.181.108.0/22, 5.181.168.0/22, 5.181.208.0/22, 5.181.252.0/22, 5.182.4.0/22, 5.182.36.0/22, 5.182.52.0/22, 5.182.64.0/22, 5.182.84.0/22, 5.182.92.0/22, 5.182.224.0/22, 5.183.28.0/22, 5.183.64.0/21, 5.183.144.0/22, 5.183.156.0/22, 5.183.180.0/22, 5.183.188.0/22, 5.183.232.0/22, 5.183.252.0/22, 5.187.40.0/21, 5.187.64.0/19, 5.188.2.0/23, 5.188.7.0/24, 5.188.8.0/22, 5.188.24.0/21, 5.188.35.0/24, 5.188.37.0/24, 5.188.38.0/23, 5.188.40.0/21, 5.188.48.0/20, 5.188.68.0/23, 5.188.72.0/21, 5.188.80.0/21, 5.188.88.0/22, 5.188.96.0/21, 5.188.104.0/22, 5.188.112.0/21, 5.188.121.0/24, 5.188.128.0/22, 5.188.136.0/21, 5.188.144.0/22, 5.188.149.0/24, 5.188.150.0/24, 5.188.156.0/22, 5.188.160.0/21, 5.188.170.0/23, 5.188.176.0/22, 5.188.184.0/22, 5.188.192.0/20, 5.188.208.0/22, 5.188.212.0/24, 5.188.216.0/21, 5.188.224.0/24, 5.188.229.0/24, 5.188.232.0/22, 5.188.236.0/23, 5.189.0.0/17, 5.189.192.0/21, 5.189.201.0/24, 5.189.205.0/24, 5.189.208.0/21, 5.189.216.0/22, 5.189.223.0/24, 5.189.224.0/19, 5.199.192.0/19, 5.200.32.0/19, 5.206.0.0/17, 5.226.152.0/21, 5.227.0.0/16, 5.228.0.0/16, 5.249.192.0/19, 5.250.168.0/21, 5.250.232.0/21, 5.252.20.0/22, 5.252.28.0/22, 5.252.64.0/22, 5.252.172.0/22, 5.252.188.0/22, 5.252.192.0/22, 5.253.60.0/23, 5.253.62.0/24, 5.253.100.0/22, 5.253.144.0/22, 5.253.164.0/22, 5.254.176.0/21, 5.254.224.0/19, 5.255.192.0/18, 23.105.224.0/19, 23.111.32.0/19, 23.111.96.0/19, 23.111.192.0/19, 31.3.16.0/20, 31.6.96.0/19, 31.7.224.0/20, 31.8.0.0/16, 31.10.0.0/23, 31.10.2.0/24, 31.10.4.0/24, 31.10.6.0/23, 31.10.8.0/21, 31.10.64.0/18, 31.12.75.0/24, 31.13.16.0/21, 31.13.32.0/19, 31.13.128.0/21, 31.13.144.0/21, 31.13.176.0/21, 31.15.20.0/22, 31.15.80.0/20, 31.22.32.0/21, 31.23.0.0/16, 31.24.24.0/21, 31.24.87.0/24, 31.25.24.0/21, 31.25.200.0/21, 31.25.240.0/21, 31.28.0.0/18, 31.28.96.0/19, 31.28.192.0/18, 31.29.128.0/17, 31.31.64.0/21, 31.31.192.0/20, 31.40.8.0/21, 31.40.32.0/19, 31.40.64.0/19, 31.40.96.0/21, 31.40.112.0/20, 31.40.128.0/18, 31.40.200.0/24, 31.40.208.0/22, 31.40.248.0/23, 31.40.252.0/22, 31.41.0.0/20, 31.41.40.0/21, 31.41.56.0/21, 31.41.96.0/21, 31.41.120.0/21, 31.41.152.0/21, 31.41.160.0/21, 31.41.184.0/21, 31.41.192.0/21, 31.41.224.0/21, 31.41.240.0/21, 31.42.32.0/20, 31.42.96.0/20, 31.42.192.0/18, 31.43.192.0/19, 31.44.8.0/21, 31.44.48.0/20, 31.44.80.0/20, 31.44.176.0/20, 31.44.240.0/20, 31.47.112.0/20, 31.47.128.0/18, 31.128.32.0/20, 31.128.48.0/21, 31.128.56.0/22, 31.128.61.0/24, 31.128.62.0/23, 31.128.128.0/19, 31.128.192.0/19, 31.129.0.0/18, 31.129.96.0/20, 31.129.122.0/23, 31.129.124.0/24, 31.129.126.0/23, 31.129.128.0/19, 31.129.192.0/19, 31.130.0.0/18, 31.130.64.0/19, 31.130.112.0/20, 31.130.128.0/19, 31.130.192.0/21, 31.130.248.0/21, 31.131.64.0/20, 31.131.80.0/21, 31.131.92.0/22, 31.131.140.0/22, 31.131.144.0/20, 31.131.184.0/22, 31.131.190.0/23, 31.131.192.0/19, 31.131.248.0/21, 31.132.32.0/22, 31.132.96.0/19, 31.132.128.0/18, 31.132.208.0/20, 31.132.224.0/19, 31.133.32.0/19, 31.133.64.0/20, 31.133.104.0/21, 31.133.120.0/21, 31.133.200.0/21, 31.133.224.0/19, 31.134.16.0/20, 31.134.32.0/20, 31.134.128.0/18, 31.134.192.0/21, 31.134.224.0/19, 31.135.8.0/21, 31.135.32.0/19, 31.135.64.0/18, 31.135.184.0/21, 31.135.224.0/19, 31.162.0.0/15, 31.170.32.0/21, 31.170.112.0/21, 31.170.168.0/21, 31.170.184.0/21, 31.171.192.0/21, 31.172.128.0/21, 31.172.192.0/19, 31.173.0.0/16, 31.177.64.0/19, 31.177.104.0/21, 31.180.0.0/15, 31.184.192.0/21, 31.184.200.0/22, 31.184.204.0/23, 31.184.208.0/20, 31.184.224.0/20, 31.184.240.0/22, 31.184.248.0/21, 31.185.8.0/21, 31.186.56.0/21, 31.186.64.0/20, 31.186.96.0/21, 31.186.128.0/19, 31.187.96.0/21, 31.192.104.0/21, 31.192.128.0/18, 31.192.232.0/21, 31.193.120.0/21, 31.200.192.0/20, 31.200.216.0/21, 31.200.224.0/20, 31.200.248.0/21, 31.204.0.0/18, 31.204.96.0/20, 31.204.160.0/19, 31.207.64.0/20, 31.207.128.0/17, 31.210.136.0/24, 31.210.138.0/23, 31.210.168.0/24, 31.210.192.0/19, 31.211.0.0/17, 31.216.160.0/20, 31.216.176.0/21, 31.217.253.0/24, 31.217.255.0/24, 31.220.152.0/21, 31.220.160.0/19, 31.222.32.0/21, 31.222.227.0/24, 31.222.241.0/24, 37.0.120.0/21, 37.1.0.0/18, 37.1.64.0/20, 37.1.80.0/21, 37.1.128.0/20, 37.8.144.0/20, 37.9.0.0/20, 37.9.36.0/22, 37.9.40.0/21, 37.9.48.0/21, 37.9.64.0/18, 37.9.128.0/21, 37.9.144.0/20, 37.9.240.0/21, 37.16.73.0/24, 37.16.80.0/21, 37.16.90.0/24, 37.18.2.0/23, 37.18.8.0/23, 37.18.10.0/24, 37.18.22.0/24, 37.18.27.0/24, 37.18.28.0/22, 37.18.32.0/23, 37.18.34.0/24, 37.18.72.0/22, 37.18.76.0/23, 37.18.83.0/24, 37.18.84.0/24, 37.18.88.0/22, 37.18.100.0/23, 37.18.102.0/24, 37.18.107.0/24, 37.18.108.0/22, 37.18.112.0/21, 37.18.120.0/23, 37.18.122.0/24, 37.18.152.0/21, 37.18.248.0/21, 37.19.0.0/21, 37.19.32.0/19, 37.19.72.0/21, 37.20.0.0/14, 37.26.232.0/21, 37.28.160.0/19, 37.29.0.0/17, 37.32.101.0/24, 37.44.40.0/21, 37.44.192.0/22, 37.44.196.0/23, 37.44.198.0/24, 37.44.252.0/22, 37.46.52.0/22, 37.46.56.0/21, 37.46.128.0/21, 37.48.248.0/21, 37.49.160.0/19, 37.49.192.0/19, 37.58.32.0/21, 37.60.16.0/21, 37.60.176.0/21, 37.60.208.0/20, 37.61.176.0/21, 37.72.64.0/19, 37.72.140.0/24, 37.72.176.0/21, 37.75.0.0/21, 37.75.192.0/20, 37.75.216.0/21, 37.75.248.0/21, 37.76.128.0/18, 37.77.104.0/21, 37.77.129.0/24, 37.77.130.0/23, 37.77.132.0/22, 37.77.150.0/24, 37.78.0.0/15, 37.98.160.0/19, 37.98.240.0/20, 37.99.216.0/21, 37.110.0.0/17, 37.110.128.0/19, 37.110.224.0/20, 37.110.240.0/21, 37.112.0.0/15, 37.114.0.0/21, 37.114.16.0/20, 37.122.0.0/17, 37.123.216.0/21, 37.128.164.0/22, 37.128.240.0/20, 37.130.192.0/21, 37.131.192.0/19, 37.139.32.0/19, 37.139.80.0/21, 37.139.96.0/20, 37.139.192.0/18, 37.140.0.0/17, 37.140.128.0/18, 37.140.192.0/21, 37.140.241.0/24, 37.140.248.0/24, 37.143.8.0/21, 37.143.16.0/20, 37.143.96.0/20, 37.144.0.0/14, 37.153.0.0/18, 37.153.64.0/21, 37.157.16.0/20, 37.157.232.0/21, 37.188.0.0/18, 37.190.0.0/17, 37.192.0.0/14, 37.200.64.0/20, 37.200.88.0/21, 37.200.120.0/21, 37.202.8.0/21, 37.203.200.0/21, 37.203.240.0/21, 37.204.0.0/16, 37.205.16.0/21, 37.205.48.0/21, 37.205.64.0/19, 37.208.64.0/20, 37.208.96.0/21, 37.208.120.0/21, 37.209.224.0/20, 37.209.240.0/21, 37.220.80.0/21, 37.220.152.0/21, 37.220.160.0/21, 37.220.176.0/20, 37.221.64.0/22, 37.221.184.0/21, 37.221.200.0/23, 37.221.203.0/24, 37.221.204.0/23, 37.221.206.0/24, 37.228.80.0/20, 37.228.112.0/21, 37.230.112.0/21, 37.230.139.0/24, 37.230.152.0/22, 37.230.168.0/23, 37.230.172.0/22, 37.230.179.0/24, 37.230.180.0/23, 37.230.184.0/23, 37.230.186.0/24, 37.230.188.0/22, 37.230.192.0/21, 37.230.201.0/24, 37.230.212.0/23, 37.230.224.0/24, 37.230.233.0/24, 37.230.248.0/24, 37.232.128.0/17, 37.233.80.0/21, 37.233.112.0/21, 37.235.64.0/21, 37.252.16.0/21, 45.8.96.0/22, 45.8.107.0/24, 45.8.108.0/22, 45.8.124.0/22, 45.8.144.0/22, 45.8.248.0/22, 45.9.24.0/22, 45.9.40.0/22, 45.9.88.0/22, 45.9.140.0/22, 45.9.164.0/22, 45.9.192.0/22, 45.9.212.0/22, 45.9.216.0/22, 45.10.40.0/22, 45.10.52.0/22, 45.10.140.0/22, 45.10.164.0/22, 45.10.216.0/22, 45.10.240.0/21, 45.11.4.0/22, 45.11.92.0/22, 45.11.112.0/22, 45.12.4.0/22, 45.12.16.0/22, 45.12.64.0/22, 45.12.72.0/22, 45.12.124.0/22, 45.12.228.0/22, 45.12.236.0/22, 45.14.20.0/22, 45.14.223.0/24, 45.15.64.0/22, 45.15.112.0/22, 45.15.156.0/22, 45.15.252.0/23, 45.15.254.0/24, 45.66.12.0/22, 45.66.116.0/22, 45.66.168.0/22, 45.66.208.0/22, 45.66.250.0/23, 45.67.24.0/22, 45.67.32.0/22, 45.67.56.0/22, 45.67.192.0/22, 45.67.212.0/23, 45.80.44.0/22, 45.80.64.0/21, 45.80.128.0/22, 45.80.204.0/22, 45.80.216.0/22, 45.80.228.0/22, 45.80.240.0/22, 45.81.164.0/22, 45.81.200.0/22, 45.81.253.0/24, 45.81.255.0/24, 45.82.12.0/22, 45.82.216.0/22, 45.82.224.0/22, 45.83.16.0/22, 45.83.92.0/22, 45.83.180.0/22, 45.84.0.0/22, 45.84.12.0/22, 45.84.60.0/22, 45.84.68.0/22, 45.84.84.0/22, 45.84.128.0/22, 45.84.152.0/22, 45.84.224.0/22, 45.85.24.0/22, 45.85.68.0/22, 45.85.140.0/22, 45.85.212.0/22, 45.86.38.0/23, 45.86.86.0/24, 45.86.180.0/22, 45.87.104.0/22, 45.87.140.0/22, 45.87.144.0/22, 45.87.152.0/22, 45.87.244.0/22, 45.88.52.0/22, 45.88.100.0/22, 45.88.252.0/22, 45.89.16.0/22, 45.89.22.0/24, 45.89.24.0/22, 45.89.52.0/22, 45.89.188.0/22, 45.89.224.0/22, 45.90.32.0/22, 45.90.45.0/24, 45.90.244.0/22, 45.91.64.0/22, 45.91.240.0/22, 45.92.174.0/24, 45.92.176.0/22, 45.93.4.0/22, 45.93.8.0/22, 45.93.13.0/24, 45.93.60.0/22, 45.93.81.0/24, 45.93.132.0/22, 45.93.156.0/22, 45.93.203.0/24, 45.93.212.0/22, 45.94.23.0/24, 45.94.76.0/22, 45.94.104.0/22, 45.94.116.0/22, 45.94.120.0/22, 45.94.140.0/22, 45.95.56.0/22, 45.95.128.0/22, 45.95.136.0/22, 45.95.232.0/22, 45.128.184.0/22, 45.128.204.0/24, 45.128.206.0/23, 45.129.76.0/23, 45.129.142.0/23, 45.129.152.0/22, 45.129.184.0/23, 45.129.187.0/24, 45.129.200.0/22, 45.130.8.0/22, 45.130.40.0/22, 45.130.145.0/24, 45.130.168.0/21, 45.130.188.0/22, 45.130.212.0/22, 45.131.40.0/22, 45.131.56.0/22, 45.131.96.0/22, 45.132.1.0/24, 45.132.176.0/22, 45.132.184.0/22, 45.133.96.0/22, 45.133.148.0/22, 45.133.234.0/24, 45.133.243.0/24, 45.134.60.0/22, 45.134.64.0/22, 45.134.124.0/22, 45.134.156.0/22, 45.134.180.0/22, 45.135.80.0/22, 45.135.92.0/22, 45.135.212.0/22, 45.136.20.0/22, 45.136.146.0/23, 45.136.180.0/22, 45.136.208.0/22, 45.137.112.0/22, 45.137.188.0/24, 45.137.252.0/22, 45.138.100.0/22, 45.138.160.0/22, 45.139.16.0/22, 45.139.24.0/22, 45.139.29.0/24, 45.139.40.0/22, 45.139.76.0/22, 45.139.144.0/22, 45.139.152.0/22, 45.139.184.0/22, 45.139.236.0/22, 45.140.52.0/22, 45.140.76.0/22, 45.140.80.0/22, 45.140.92.0/22, 45.140.176.0/22, 45.141.76.0/22, 45.141.84.0/22, 45.141.208.0/22, 45.141.219.0/24, 45.142.32.0/22, 45.142.208.0/22, 45.143.20.0/22, 45.143.84.0/22, 45.143.136.0/22, 45.143.152.0/22, 45.143.164.0/23, 45.143.200.0/22, 45.143.236.0/22, 45.144.1.0/24, 45.144.40.0/21, 45.144.176.0/22, 45.144.220.0/22, 45.145.4.0/22, 45.145.64.0/22, 45.145.116.0/22, 45.145.163.0/24, 45.145.168.0/24, 45.145.188.0/22, 45.146.64.0/22, 45.146.152.0/22, 45.146.164.0/22, 45.147.80.0/22, 45.147.120.0/22, 45.147.144.0/21, 45.147.162.0/23, 45.147.176.0/22, 45.147.195.0/24, 45.148.124.0/22, 45.148.164.0/24, 45.148.167.0/24, 45.148.244.0/22, 45.149.40.0/22, 45.149.244.0/22, 45.150.8.0/22, 45.150.36.0/22, 45.150.110.0/23, 45.150.148.0/24, 45.150.188.0/22, 45.150.204.0/22, 45.151.30.0/23, 45.151.60.0/22, 45.151.96.0/22, 45.151.108.0/22, 45.151.136.0/22, 45.151.228.0/22, 45.151.232.0/22, 45.152.24.0/22, 45.152.84.0/22, 45.152.112.0/22, 45.152.120.0/22, 45.152.168.0/22, 45.152.220.0/22, 45.153.12.0/22, 45.153.28.0/22, 45.153.44.0/22, 45.153.72.0/22, 45.153.188.0/22, 45.153.196.0/22, 45.154.0.0/22, 45.154.184.0/22, 45.155.6.0/24, 45.155.60.0/24, 45.155.92.0/22, 45.155.104.0/22, 45.155.144.0/22, 45.155.204.0/23, 45.155.236.0/22, 45.156.0.0/22, 45.156.20.0/22, 45.156.24.0/22, 45.156.212.0/22, 45.157.140.0/22, 45.157.160.0/22, 45.157.212.0/22, 45.158.188.0/22, 45.158.232.0/22, 45.159.0.0/22, 45.159.16.0/22, 45.159.104.0/22, 45.159.124.0/22, 45.159.136.0/22, 45.159.200.0/22, 45.159.208.0/22, 45.159.248.0/22, 46.0.0.0/16, 46.16.8.0/21, 46.16.24.0/21, 46.16.34.0/24, 46.16.36.0/24, 46.16.64.0/21, 46.16.96.0/21, 46.16.176.0/21, 46.16.224.0/21, 46.17.40.0/21, 46.17.48.0/21, 46.17.200.0/21, 46.18.200.0/21, 46.19.64.0/21, 46.19.112.0/21, 46.20.64.0/20, 46.20.176.0/20, 46.21.64.0/20, 46.21.80.0/22, 46.21.244.0/22, 46.21.248.0/23, 46.21.252.0/22, 46.22.48.0/20, 46.22.240.0/20, 46.23.144.0/20, 46.23.176.0/20, 46.28.16.0/21, 46.28.88.0/21, 46.28.128.0/21, 46.28.224.0/21, 46.29.8.0/21, 46.29.37.0/24, 46.29.38.0/24, 46.29.72.0/21, 46.29.112.0/21, 46.29.152.0/21, 46.29.160.0/21, 46.29.192.0/21, 46.29.208.0/21, 46.29.232.0/21, 46.30.32.0/20, 46.30.152.0/21, 46.31.0.0/21, 46.31.24.0/21, 46.31.67.0/24, 46.31.152.0/21, 46.32.64.0/19, 46.34.128.0/23, 46.34.132.0/22, 46.34.136.0/21, 46.34.144.0/23, 46.34.148.0/22, 46.34.152.0/21, 46.35.224.0/19, 46.36.0.0/19, 46.37.128.0/19, 46.38.0.0/18, 46.38.96.0/19, 46.39.0.0/18, 46.39.224.0/19, 46.41.64.0/18, 46.42.0.0/18, 46.42.128.0/18, 46.43.192.0/18, 46.44.0.0/18, 46.45.0.0/18, 46.45.192.0/18, 46.46.0.0/18, 46.46.128.0/18, 46.47.0.0/18, 46.47.192.0/18, 46.48.0.0/16, 46.50.128.0/17, 46.52.128.0/17, 46.61.0.0/16, 46.62.0.0/17, 46.63.128.0/17, 46.72.0.0/15, 46.102.191.0/24, 46.111.0.0/16, 46.138.0.0/16, 46.146.0.0/15, 46.148.48.0/20, 46.148.96.0/20, 46.148.128.0/20, 46.148.176.0/20, 46.148.192.0/18, 46.149.32.0/20, 46.149.64.0/23, 46.149.99.0/24, 46.149.101.0/24, 46.149.110.0/24, 46.149.128.0/20, 46.149.164.0/22, 46.149.175.0/24, 46.149.224.0/20, 46.150.128.0/18, 46.150.224.0/20, 46.151.0.0/20, 46.151.40.0/21, 46.151.48.0/21, 46.151.64.0/21, 46.151.88.0/21, 46.151.104.0/21, 46.151.144.0/20, 46.151.168.0/21, 46.151.240.0/21, 46.158.0.0/15, 46.160.0.0/18, 46.160.128.0/17, 46.161.0.0/18, 46.161.128.0/18, 46.162.0.0/18, 46.163.128.0/18, 46.164.192.0/18, 46.165.0.0/18, 46.166.64.0/18, 46.166.192.0/18, 46.167.64.0/18, 46.172.0.0/18, 46.172.96.0/19, 46.172.160.0/19, 46.172.192.0/19, 46.173.0.0/20, 46.173.16.0/21, 46.173.24.0/22, 46.173.28.0/23, 46.173.32.0/19, 46.173.176.0/20, 46.173.208.0/20, 46.174.8.0/21, 46.174.40.0/21, 46.174.48.0/21, 46.174.80.0/21, 46.174.104.0/21, 46.174.112.0/21, 46.174.248.0/21, 46.175.12.0/24, 46.175.24.0/21, 46.175.32.0/21, 46.175.96.0/21, 46.175.120.0/21, 46.175.192.0/19, 46.180.0.0/15, 46.182.24.0/21, 46.182.128.0/21, 46.183.0.0/21, 46.183.80.0/21, 46.183.128.0/21, 46.183.160.0/21, 46.183.176.0/21, 46.187.0.0/17, 46.188.0.0/17, 46.191.128.0/17, 46.226.64.0/21, 46.226.96.0/21, 46.226.122.0/24, 46.226.224.0/21, 46.226.240.0/21, 46.227.24.0/21, 46.227.160.0/22, 46.227.164.0/24, 46.227.166.0/23, 46.227.208.0/21, 46.228.0.0/20, 46.228.64.0/20, 46.228.96.0/19, 46.229.64.0/20, 46.229.96.0/20, 46.229.128.0/20, 46.229.176.0/20, 46.229.208.0/20, 46.231.168.0/21, 46.231.208.0/21, 46.232.160.0/21, 46.232.200.0/21, 46.232.216.0/21, 46.232.240.0/21, 46.233.192.0/18, 46.235.48.0/21, 46.235.64.0/21, 46.235.78.0/23, 46.235.160.0/21, 46.235.184.0/21, 46.235.216.0/21, 46.235.248.0/21, 46.236.128.0/18, 46.237.0.0/18, 46.241.0.0/17, 46.242.0.0/17, 46.243.8.0/21, 46.243.60.0/22, 46.243.141.0/24, 46.243.142.0/23, 46.243.172.0/24, 46.243.180.0/22, 46.243.184.0/22, 46.243.201.0/24, 46.243.205.0/24, 46.243.206.0/24, 46.243.209.0/24, 46.243.210.0/23, 46.243.212.0/24, 46.243.218.0/23, 46.243.226.0/23, 46.243.244.0/23, 46.243.254.0/24, 46.245.128.0/21, 46.245.168.0/21, 46.245.234.0/24, 46.249.0.0/19, 46.250.32.0/19, 46.250.64.0/19, 46.250.128.0/19, 46.251.64.0/19, 46.252.112.0/20, 46.252.160.0/20, 46.252.240.0/20, 46.253.132.0/24, 46.253.143.0/24, 46.254.16.0/20, 46.254.80.0/21, 46.254.92.0/22, 46.254.160.0/21, 46.254.216.0/21, 46.254.240.0/21, 46.255.25.0/24, 46.255.96.0/21, 46.255.136.0/21, 51.250.0.0/16, 62.3.34.0/24, 62.3.63.0/24, 62.5.128.0/17, 62.16.32.0/19, 62.16.96.0/19, 62.32.64.0/21, 62.32.72.0/22, 62.32.76.0/23, 62.32.79.0/24, 62.32.80.0/22, 62.32.86.0/23, 62.32.88.0/22, 62.32.94.0/23, 62.33.0.0/16, 62.61.0.0/19, 62.63.64.0/18, 62.64.0.0/19, 62.68.128.0/19, 62.69.0.0/19, 62.76.0.0/17, 62.76.128.0/18, 62.76.192.0/19, 62.76.240.0/20, 62.78.32.0/19, 62.78.80.0/20, 62.84.104.0/21, 62.84.112.0/20, 62.89.192.0/18, 62.105.0.0/18, 62.105.128.0/19, 62.106.76.0/24, 62.106.96.0/19, 62.109.0.0/19, 62.109.160.0/19, 62.112.96.0/19, 62.113.32.0/19, 62.113.64.0/18, 62.117.64.0/18, 62.118.0.0/16, 62.122.48.0/21, 62.122.88.0/21, 62.122.96.0/21, 62.122.128.0/21, 62.122.144.0/21, 62.122.176.0/21, 62.122.192.0/21, 62.122.208.0/21, 62.122.240.0/21, 62.133.60.0/22, 62.133.160.0/19, 62.140.224.0/19, 62.141.64.0/18, 62.148.0.0/19, 62.148.128.0/19, 62.148.224.0/19, 62.152.32.0/19, 62.152.64.0/19, 62.152.192.0/19, 62.165.0.0/18, 62.168.224.0/19, 62.173.128.0/19, 62.176.0.0/19, 62.177.32.0/19, 62.181.32.0/19, 62.182.8.0/21, 62.182.24.0/21, 62.182.48.0/21, 62.182.64.0/20, 62.182.88.0/21, 62.182.128.0/20, 62.182.156.0/22, 62.182.200.0/21, 62.183.0.0/17, 62.192.32.0/19, 62.192.224.0/19, 62.204.32.0/24, 62.204.57.0/24, 62.205.160.0/19, 62.213.0.0/17, 62.217.160.0/19, 62.220.32.0/19, 62.231.0.0/19, 62.231.160.0/19, 62.233.39.0/24, 62.233.42.0/23, 62.245.32.0/19, 62.249.128.0/19, 77.34.0.0/15, 77.37.128.0/17, 77.39.0.0/17, 77.40.0.0/17, 77.41.0.0/16, 77.43.128.0/17, 77.45.128.0/17, 77.50.0.0/15, 77.66.128.0/17, 77.72.120.0/21, 77.72.136.0/21, 77.72.240.0/20, 77.73.24.0/21, 77.73.48.0/21, 77.73.88.0/21, 77.73.136.0/21, 77.73.232.0/21, 77.74.8.0/21, 77.74.24.0/21, 77.74.72.0/21, 77.74.184.0/21, 77.75.0.0/20, 77.75.128.0/21, 77.75.152.0/21, 77.75.168.0/21, 77.79.128.0/18, 77.81.5.0/24, 77.82.0.0/16, 77.83.0.0/24, 77.83.8.0/22, 77.83.16.0/22, 77.83.52.0/22, 77.83.72.0/24, 77.83.116.0/22, 77.83.152.0/22, 77.83.244.0/22, 77.87.64.0/21, 77.87.80.0/21, 77.87.96.0/21, 77.87.112.0/21, 77.87.124.0/24, 77.87.168.0/21, 77.87.200.0/21, 77.87.208.0/21, 77.88.0.0/18, 77.91.64.0/21, 77.91.72.0/22, 77.91.78.0/23, 77.91.84.0/22, 77.91.88.0/21, 77.91.192.0/21, 77.91.208.0/24, 77.91.224.0/21, 77.93.96.0/19, 77.94.32.0/20, 77.94.48.0/21, 77.94.96.0/19, 77.94.160.0/19, 77.94.192.0/19, 77.95.8.0/21, 77.95.20.0/22, 77.95.88.0/21, 77.95.128.0/21, 77.95.169.0/24, 77.95.170.0/24, 77.95.200.0/21, 77.105.145.0/24, 77.105.146.0/23, 77.105.148.0/22, 77.105.152.0/21, 77.105.165.0/24, 77.105.166.0/23, 77.105.168.0/21, 77.105.176.0/20, 77.106.0.0/17, 77.106.192.0/18, 77.108.64.0/18, 77.108.192.0/18, 77.120.56.0/22, 77.121.14.0/24, 77.121.32.0/19, 77.123.192.0/18, 77.220.32.0/19, 77.220.128.0/18, 77.220.192.0/22, 77.220.208.0/22, 77.220.216.0/21, 77.221.128.0/22, 77.221.192.0/19, 77.222.32.0/19, 77.222.96.0/19, 77.223.64.0/18, 77.232.0.0/18, 77.232.128.0/19, 77.232.160.0/21, 77.232.184.0/21, 77.233.0.0/19, 77.233.160.0/19, 77.233.192.0/19, 77.234.0.0/19, 77.234.192.0/19, 77.235.192.0/19, 77.236.32.0/19, 77.236.64.0/19, 77.236.224.0/19, 77.238.96.0/21, 77.238.128.0/19, 77.239.192.0/19, 77.240.144.0/20, 77.240.160.0/20, 77.241.16.0/20, 77.241.32.0/20, 77.241.144.0/20, 77.241.240.0/20, 77.242.0.0/20, 77.242.96.0/20, 77.243.0.0/20, 77.243.96.0/19, 77.243.240.0/20, 77.244.16.0/20, 77.244.64.0/20, 77.244.160.0/20, 77.244.208.0/20, 77.245.112.0/20, 77.245.160.0/20, 77.245.192.0/19, 77.246.144.0/21, 77.246.156.0/22, 77.246.224.0/20, 77.246.248.0/21, 77.247.128.0/20, 77.247.160.0/20, 77.247.184.0/21, 77.247.208.0/21, 77.247.232.0/21, 77.247.240.0/21, 78.24.0.0/21, 78.24.24.0/21, 78.24.40.0/21, 78.24.56.0/21, 78.24.92.0/22, 78.24.100.0/22, 78.24.152.0/21, 78.24.176.0/21, 78.24.204.0/24, 78.24.216.0/21, 78.24.224.0/21, 78.25.64.0/18, 78.25.128.0/18, 78.29.0.0/17, 78.30.192.0/18, 78.31.0.0/21, 78.31.16.0/21, 78.31.72.0/21, 78.31.96.0/21, 78.31.160.0/23, 78.31.177.0/24, 78.31.178.0/23, 78.31.180.0/22, 78.31.210.0/24, 78.31.216.0/21, 78.31.240.0/21, 78.36.0.0/15, 78.40.24.0/21, 78.40.80.0/21, 78.40.116.0/22, 78.40.184.0/21, 78.40.216.0/21, 78.41.32.0/21, 78.41.84.0/22, 78.41.88.0/21, 78.41.96.0/20, 78.41.142.0/24, 78.41.176.0/21, 78.41.192.0/21, 78.81.0.0/16, 78.85.0.0/16, 78.106.0.0/15, 78.108.64.0/19, 78.108.192.0/20, 78.109.32.0/20, 78.109.112.0/20, 78.109.128.0/20, 78.110.48.0/20, 78.110.144.0/20, 78.110.240.0/20, 78.111.80.0/22, 78.111.92.0/22, 78.111.144.0/20, 78.111.240.0/20, 78.132.128.0/17, 78.136.192.0/19, 78.136.224.0/20, 78.136.240.0/21, 78.136.248.0/22, 78.136.252.0/23, 78.137.104.0/21, 78.138.128.0/18, 78.139.64.0/18, 78.139.192.0/18, 78.140.0.0/18, 78.140.192.0/19, 78.140.224.0/20, 78.140.240.0/21, 78.140.248.0/22, 78.142.220.0/22, 78.142.232.0/22, 78.153.0.0/19, 78.153.128.0/19, 78.155.160.0/19, 78.155.192.0/19, 78.156.224.0/19, 78.157.224.0/19, 78.158.192.0/19, 78.159.224.0/19, 79.98.8.0/21, 79.98.88.0/21, 79.98.136.0/21, 79.98.184.0/22, 79.98.200.0/21, 79.98.208.0/21, 79.98.248.0/21, 79.99.16.0/21, 79.99.48.0/21, 79.99.104.0/21, 79.99.216.0/21, 79.99.240.0/21, 79.104.0.0/15, 79.110.188.0/22, 79.110.226.0/24, 79.110.240.0/20, 79.111.0.0/16, 79.120.0.0/17, 79.122.128.0/17, 79.126.0.0/17, 79.132.96.0/19, 79.133.64.0/19, 79.133.127.0/24, 79.133.128.0/19, 79.133.160.0/21, 79.133.172.0/22, 79.133.176.0/23, 79.133.181.0/24, 79.133.182.0/23, 79.134.0.0/19, 79.134.64.0/19, 79.134.192.0/19, 79.135.64.0/19, 79.135.224.0/19, 79.136.128.0/17, 79.137.128.0/19, 79.137.160.0/21, 79.137.168.0/23, 79.137.172.0/22, 79.137.180.0/22, 79.137.184.0/21, 79.137.192.0/18, 79.139.128.0/17, 79.140.16.0/20, 79.140.64.0/20, 79.140.96.0/20, 79.140.240.0/20, 79.141.48.0/20, 79.141.64.0/20, 79.141.208.0/20, 79.142.16.0/20, 79.142.80.0/21, 79.142.88.0/22, 79.142.92.0/24, 79.142.96.0/20, 79.142.160.0/20, 79.143.0.0/20, 79.143.24.0/21, 79.143.64.0/20, 79.143.224.0/20, 79.164.0.0/15, 79.170.24.0/21, 79.170.136.0/21, 79.170.152.0/21, 79.170.160.0/21, 79.171.8.0/21, 79.171.112.0/22, 79.171.116.0/24, 79.171.152.0/24, 79.171.168.0/21, 79.171.208.0/21, 79.172.0.0/17, 79.173.64.0/18, 79.174.12.0/22, 79.174.32.0/19, 79.174.64.0/19, 79.174.184.0/21, 79.175.0.0/18, 80.64.96.0/20, 80.64.160.0/20, 80.65.16.0/20, 80.66.64.0/19, 80.66.144.0/20, 80.67.40.0/22, 80.67.48.0/20, 80.67.208.0/20, 80.67.240.0/20, 80.68.0.0/20, 80.68.64.0/20, 80.68.240.0/20, 80.69.144.0/20, 80.69.176.0/20, 80.70.96.0/20, 80.70.224.0/20, 80.71.154.0/24, 80.71.156.0/24, 80.71.160.0/19, 80.71.208.0/20, 80.71.240.0/20, 80.72.28.0/22, 80.72.112.0/20, 80.72.208.0/20, 80.72.224.0/20, 80.73.16.0/20, 80.73.64.0/19, 80.73.160.0/20, 80.73.192.0/20, 80.75.80.0/20, 80.75.128.0/20, 80.75.223.0/24, 80.76.40.0/22, 80.76.96.0/20, 80.76.128.0/20, 80.76.176.0/20, 80.76.224.0/19, 80.77.160.0/20, 80.78.96.0/19, 80.78.192.0/20, 80.78.240.0/20, 80.79.64.0/20, 80.79.176.0/20, 80.79.240.0/20, 80.80.96.0/19, 80.80.192.0/20, 80.81.208.0/20, 80.82.32.0/19, 80.82.80.0/20, 80.82.160.0/19, 80.83.192.0/20, 80.83.224.0/19, 80.84.112.0/20, 80.85.144.0/20, 80.85.176.0/20, 80.85.240.0/22, 80.85.248.0/21, 80.87.96.0/20, 80.87.144.0/20, 80.87.192.0/20, 80.88.48.0/20, 80.89.0.0/20, 80.89.128.0/19, 80.89.192.0/20, 80.90.64.0/20, 80.90.112.0/20, 80.90.176.0/20, 80.90.240.0/20, 80.91.16.0/20, 80.91.192.0/20, 80.92.0.0/19, 80.92.32.0/20, 80.92.96.0/20, 80.92.160.0/20, 80.92.207.0/24, 80.92.208.0/20, 80.93.48.0/20, 80.93.96.0/20, 80.93.176.0/20, 80.94.84.0/22, 80.95.32.0/20, 80.115.192.0/19, 80.234.0.0/17, 80.237.0.0/17, 80.240.32.0/19, 80.240.100.0/24, 80.240.102.0/24, 80.240.110.0/24, 80.240.208.0/20, 80.240.240.0/20, 80.242.48.0/22, 80.242.64.0/19, 80.242.96.0/20, 80.243.0.0/20, 80.243.64.0/20, 80.244.0.0/24, 80.244.5.0/24, 80.244.32.0/20, 80.244.224.0/20, 80.245.112.0/20, 80.245.240.0/20, 80.246.16.0/21, 80.246.24.0/22, 80.246.64.0/19, 80.246.208.0/20, 80.246.240.0/20, 80.247.32.0/20, 80.247.96.0/19, 80.247.176.0/20, 80.248.48.0/20, 80.248.80.0/20, 80.248.144.0/20, 80.249.129.0/24, 80.249.131.0/24, 80.249.135.0/24, 80.249.136.0/24, 80.249.144.0/21, 80.249.176.0/20, 80.249.192.0/20, 80.250.64.0/19, 80.250.160.0/19, 80.250.208.0/20, 80.250.224.0/20, 80.251.48.0/20, 80.251.112.0/20, 80.251.128.0/20, 80.251.144.0/21, 80.251.154.0/23, 80.251.156.0/22, 80.251.224.0/20, 80.252.16.0/20, 80.252.128.0/19, 80.253.0.0/19, 80.253.224.0/20, 80.254.16.0/20, 80.254.48.0/20, 80.254.96.0/19, 80.255.16.0/20, 80.255.80.0/20, 80.255.128.0/19, 80.255.176.0/20, 81.1.128.0/17, 81.2.0.0/18, 81.3.128.0/18, 81.4.192.0/18, 81.5.64.0/18, 81.9.0.0/17, 81.13.0.0/17, 81.16.80.0/20, 81.16.208.0/22, 81.17.0.0/20, 81.17.144.0/20, 81.18.112.0/20, 81.18.128.0/19, 81.19.64.0/19, 81.19.128.0/20, 81.20.96.0/20, 81.20.160.0/20, 81.20.192.0/20, 81.22.0.0/20, 81.22.46.0/23, 81.22.48.0/20, 81.22.192.0/22, 81.22.200.0/21, 81.22.208.0/21, 81.23.0.0/20, 81.23.96.0/19, 81.23.144.0/20, 81.23.160.0/19, 81.23.192.0/20, 81.24.80.0/20, 81.24.112.0/20, 81.24.128.0/20, 81.24.176.0/20, 81.25.0.0/20, 81.25.48.0/20, 81.25.64.0/22, 81.26.80.0/20, 81.26.128.0/20, 81.26.144.0/21, 81.26.152.0/22, 81.26.157.0/24, 81.26.176.0/20, 81.27.48.0/20, 81.27.144.0/20, 81.27.240.0/20, 81.28.0.0/22, 81.28.160.0/19, 81.28.208.0/20, 81.28.244.0/22, 81.29.112.0/20, 81.29.128.0/20, 81.30.48.0/20, 81.30.112.0/20, 81.30.176.0/20, 81.30.192.0/19, 81.88.80.0/20, 81.88.112.0/20, 81.88.208.0/20, 81.89.64.0/20, 81.89.80.0/21, 81.89.112.0/20, 81.89.176.0/20, 81.90.0.0/20, 81.90.180.0/22, 81.90.208.0/20, 81.91.32.0/19, 81.91.189.0/24, 81.94.16.0/20, 81.94.32.0/20, 81.94.128.0/19, 81.95.16.0/20, 81.95.32.0/20, 81.95.128.0/20, 81.95.208.0/20, 81.161.16.0/20, 81.161.32.0/20, 81.161.63.0/24, 81.161.98.0/23, 81.161.114.0/24, 81.161.116.0/22, 81.161.120.0/21, 81.161.208.0/20, 81.162.0.0/19, 81.162.32.0/20, 81.162.48.0/22, 81.162.52.0/23, 81.162.56.0/21, 81.162.96.0/19, 81.163.16.0/20, 81.163.32.0/19, 81.163.64.0/21, 81.163.80.0/20, 81.163.96.0/20, 81.163.128.0/18, 81.163.224.0/19, 81.176.0.0/15, 81.195.0.0/16, 81.200.0.0/19, 81.200.80.0/20, 81.200.112.0/20, 81.200.144.0/20, 81.200.208.0/20, 81.200.240.0/20, 81.201.16.0/20, 81.201.64.0/20, 81.201.240.0/20, 81.211.0.0/17, 81.222.0.0/16, 82.96.192.0/18, 82.97.194.0/23, 82.97.198.0/24, 82.97.201.0/24, 82.97.202.0/24, 82.97.207.0/24, 82.112.0.0/18, 82.112.176.0/20, 82.114.0.0/19, 82.114.96.0/19, 82.114.128.0/19, 82.114.224.0/19, 82.116.0.0/18, 82.117.64.0/19, 82.117.160.0/19, 82.118.128.0/19, 82.119.128.0/19, 82.137.128.0/18, 82.138.0.0/18, 82.140.64.0/18, 82.140.192.0/18, 82.142.128.0/18, 82.144.64.0/19, 82.146.32.0/19, 82.147.64.0/18, 82.148.8.0/21, 82.148.16.0/20, 82.149.128.0/19, 82.149.192.0/19, 82.151.96.0/19, 82.151.192.0/19, 82.162.0.0/16, 82.179.0.0/16, 82.193.128.0/19, 82.194.160.0/19, 82.194.224.0/19, 82.195.0.0/19, 82.196.64.0/19, 82.196.128.0/19, 82.198.0.0/19, 82.198.160.0/19, 82.199.96.0/19, 82.200.0.0/17, 82.202.128.0/19, 82.202.160.0/20, 82.202.176.0/21, 82.202.186.0/23, 82.202.188.0/22, 82.202.192.0/18, 82.204.128.0/17, 82.208.64.0/18, 82.209.64.0/18, 83.68.32.0/19, 83.69.0.0/19, 83.69.64.0/18, 83.69.160.0/19, 83.69.192.0/18, 83.97.0.0/21, 83.97.104.0/21, 83.102.128.0/17, 83.136.24.0/21, 83.136.112.0/21, 83.136.222.0/24, 83.136.232.0/22, 83.136.236.0/24, 83.136.238.0/23, 83.136.240.0/21, 83.137.48.0/21, 83.137.216.0/21, 83.138.59.0/24, 83.139.128.0/18, 83.142.8.0/21, 83.142.160.0/21, 83.143.32.0/21, 83.143.48.0/21, 83.143.64.0/22, 83.143.152.0/21, 83.143.192.0/20, 83.146.64.0/18, 83.149.0.0/18, 83.149.192.0/18, 83.151.0.0/20, 83.166.96.0/19, 83.166.224.0/19, 83.167.0.0/19, 83.167.64.0/18, 83.169.192.0/18, 83.171.64.0/18, 83.171.224.0/22, 83.171.240.0/22, 83.171.252.0/22, 83.172.0.0/18, 83.174.192.0/18, 83.217.0.0/18, 83.217.192.0/19, 83.219.0.0/19, 83.219.128.0/19, 83.219.224.0/19, 83.220.32.0/19, 83.220.64.0/19, 83.220.160.0/19, 83.220.224.0/19, 83.221.0.0/19, 83.221.192.0/19, 83.222.0.0/22, 83.222.4.0/23, 83.222.8.0/21, 83.222.16.0/20, 83.222.64.0/18, 83.222.192.0/19, 83.229.14.0/24, 83.229.25.0/24, 83.229.128.0/17, 83.234.0.0/16, 83.237.0.0/16, 83.239.0.0/16, 83.242.96.0/22, 83.242.102.0/23, 83.242.104.0/21, 83.242.128.0/17, 83.243.72.0/21, 83.246.128.0/17, 84.16.128.0/19, 84.17.0.0/19, 84.17.224.0/19, 84.18.96.0/19, 84.21.64.0/19, 84.21.164.0/22, 84.21.224.0/19, 84.22.128.0/19, 84.22.192.0/19, 84.23.32.0/19, 84.38.0.0/24, 84.38.2.0/23, 84.38.6.0/23, 84.38.176.0/20, 84.39.128.0/21, 84.39.240.0/20, 84.42.0.0/17, 84.47.128.0/22, 84.47.134.0/23, 84.47.136.0/21, 84.47.144.0/23, 84.47.147.0/24, 84.47.148.0/22, 84.47.152.0/23, 84.47.156.0/22, 84.47.160.0/22, 84.47.164.0/23, 84.47.168.0/21, 84.47.176.0/23, 84.47.178.0/24, 84.47.181.0/24, 84.47.182.0/23, 84.47.186.0/23, 84.47.188.0/24, 84.47.190.0/23, 84.51.64.0/18, 84.51.192.0/19, 84.52.64.0/18, 84.53.192.0/18, 84.54.28.0/22, 84.54.36.0/22, 84.54.44.0/22, 84.54.55.0/24, 84.54.56.0/22, 84.54.192.0/18, 84.201.128.0/18, 84.201.240.0/20, 84.204.0.0/16, 84.234.104.0/22, 84.237.0.0/17, 84.242.0.0/21, 84.242.192.0/18, 84.244.0.0/18, 84.246.105.0/24, 84.252.128.0/20, 84.252.144.0/21, 84.252.152.0/22, 84.252.160.0/19, 84.253.64.0/18, 84.254.192.0/18, 85.8.155.0/24, 85.8.194.0/24, 85.8.201.0/24, 85.8.215.0/24, 85.12.192.0/18, 85.15.64.0/18, 85.15.128.0/18, 85.21.0.0/16, 85.26.128.0/17, 85.28.0.0/18, 85.28.192.0/18, 85.30.192.0/18, 85.31.36.0/22, 85.31.112.0/20, 85.31.176.0/21, 85.88.160.0/19, 85.89.96.0/19, 85.90.96.0/19, 85.91.96.0/21, 85.91.104.0/24, 85.91.106.0/24, 85.91.112.0/22, 85.91.118.0/23, 85.91.120.0/23, 85.91.122.0/24, 85.91.192.0/19, 85.92.0.0/19, 85.92.108.0/22, 85.92.116.0/23, 85.92.118.0/24, 85.93.32.0/19, 85.93.128.0/19, 85.94.0.0/18, 85.95.128.0/18, 85.112.32.0/19, 85.112.96.0/19, 85.113.32.0/19, 85.113.128.0/19, 85.113.192.0/19, 85.114.0.0/19, 85.114.64.0/19, 85.114.160.0/19, 85.115.160.0/19, 85.115.200.0/22, 85.115.224.0/19, 85.116.96.0/19, 85.117.64.0/19, 85.117.234.0/23, 85.118.136.0/21, 85.118.176.0/21, 85.118.224.0/21, 85.119.72.0/21, 85.119.144.0/21, 85.140.0.0/14, 85.158.48.0/21, 85.158.88.0/21, 85.159.32.0/20, 85.159.116.0/24, 85.159.224.0/21, 85.172.0.0/14, 85.192.0.0/18, 85.192.128.0/18, 85.193.64.0/21, 85.193.72.0/22, 85.193.80.0/20, 85.198.64.0/20, 85.198.80.0/21, 85.198.92.0/22, 85.198.96.0/20, 85.198.112.0/21, 85.202.0.0/20, 85.202.128.0/20, 85.202.176.0/20, 85.202.224.0/19, 85.204.240.0/24, 85.208.85.0/24, 85.208.86.0/23, 85.208.209.0/24, 85.208.210.0/23, 85.208.220.0/22, 85.209.68.0/22, 85.209.144.0/22, 85.209.149.0/24, 85.209.150.0/23, 85.209.196.0/22, 85.209.212.0/22, 85.232.96.0/19, 85.233.64.0/19, 85.233.128.0/19, 85.234.0.0/18, 85.234.96.0/19, 85.235.32.0/19, 85.235.82.0/23, 85.235.160.0/19, 85.235.192.0/19, 85.236.0.0/19, 85.236.160.0/19, 85.237.32.0/19, 85.239.32.0/19, 85.249.0.0/16, 85.255.184.0/21, 86.62.4.0/22, 86.62.12.0/22, 86.62.48.0/22, 86.62.64.0/18, 86.102.0.0/16, 86.109.192.0/19, 86.110.0.0/19, 86.110.96.0/19, 86.110.160.0/19, 86.110.192.0/21, 86.110.200.0/22, 86.110.208.0/20, 86.111.0.0/19, 87.76.0.0/20, 87.103.128.0/17, 87.117.0.0/18, 87.117.128.0/18, 87.118.192.0/18, 87.119.224.0/19, 87.224.128.0/17, 87.225.0.0/17, 87.226.128.0/17, 87.228.0.0/17, 87.229.128.0/17, 87.236.8.0/21, 87.236.16.0/20, 87.236.37.0/24, 87.236.40.0/21, 87.236.80.0/21, 87.236.184.0/21, 87.237.40.0/21, 87.237.112.0/21, 87.237.136.0/21, 87.238.96.0/21, 87.238.232.0/21, 87.239.0.0/21, 87.239.24.0/21, 87.239.32.0/21, 87.239.104.0/21, 87.239.144.0/21, 87.240.0.0/18, 87.240.128.0/18, 87.241.192.0/18, 87.242.64.0/18, 87.244.0.0/18, 87.245.128.0/18, 87.245.192.0/20, 87.247.132.0/22, 87.248.224.0/19, 87.249.0.0/18, 87.249.192.0/18, 87.250.0.0/19, 87.250.192.0/18, 87.251.64.0/18, 87.251.160.0/19, 87.253.0.0/19, 87.253.192.0/19, 87.254.128.0/19, 87.255.0.0/19, 87.255.224.0/19, 88.80.32.0/19, 88.81.32.0/19, 88.81.192.0/19, 88.82.64.0/19, 88.82.160.0/19, 88.83.192.0/19, 88.84.192.0/19, 88.85.160.0/19, 88.85.192.0/19, 88.86.64.0/19, 88.86.192.0/19, 88.87.64.0/19, 88.135.0.0/20, 88.135.48.0/20, 88.135.80.0/20, 88.135.112.0/20, 88.147.128.0/17, 88.151.88.0/21, 88.151.104.0/21, 88.151.114.0/24, 88.151.184.0/21, 88.151.200.0/21, 88.151.248.0/21, 88.200.128.0/17, 88.201.128.0/17, 88.204.0.0/17, 88.205.128.0/17, 88.206.0.0/17, 88.210.0.0/23, 88.210.2.0/24, 88.210.4.0/23, 88.210.7.0/24, 88.210.8.0/24, 88.210.15.0/24, 88.210.16.0/22, 88.210.22.0/23, 88.210.24.0/21, 88.210.32.0/22, 88.210.40.0/21, 88.210.48.0/21, 88.210.56.0/22, 88.212.192.0/18, 88.214.32.0/22, 88.215.128.0/18, 88.218.56.0/21, 88.218.65.0/24, 88.218.66.0/23, 88.218.168.0/22, 88.218.212.0/22, 88.218.216.0/22, 88.218.244.0/22, 89.16.32.0/19, 89.16.96.0/19, 89.17.32.0/19, 89.18.128.0/19, 89.19.56.0/22, 89.19.160.0/19, 89.19.192.0/19, 89.20.0.0/19, 89.20.32.0/20, 89.20.52.0/22, 89.20.56.0/21, 89.20.96.0/19, 89.20.128.0/19, 89.21.128.0/19, 89.22.16.0/20, 89.22.52.0/22, 89.22.56.0/21, 89.22.128.0/18, 89.22.216.0/21, 89.22.224.0/19, 89.23.0.0/18, 89.23.96.0/20, 89.23.112.0/21, 89.23.120.0/22, 89.23.126.0/24, 89.23.128.0/18, 89.23.192.0/19, 89.28.160.0/21, 89.28.192.0/21, 89.28.248.0/21, 89.31.16.0/21, 89.31.32.0/21, 89.31.80.0/20, 89.31.104.0/21, 89.31.112.0/21, 89.31.184.0/21, 89.31.220.0/22, 89.40.204.0/24, 89.40.233.0/24, 89.104.64.0/19, 89.104.96.0/22, 89.104.103.0/24, 89.104.104.0/22, 89.104.108.0/23, 89.104.112.0/21, 89.104.120.0/24, 89.104.124.0/22, 89.105.128.0/19, 89.106.168.0/21, 89.106.192.0/21, 89.106.248.0/21, 89.107.11.0/24, 89.107.32.0/21, 89.107.80.0/21, 89.107.112.0/20, 89.107.136.0/21, 89.107.192.0/20, 89.108.64.0/18, 89.109.0.0/18, 89.109.128.0/17, 89.110.0.0/18, 89.111.128.0/18, 89.112.0.0/15, 89.145.128.0/19, 89.148.192.0/18, 89.151.128.0/18, 89.163.0.0/17, 89.169.0.0/19, 89.169.36.0/22, 89.169.40.0/21, 89.169.48.0/22, 89.169.56.0/21, 89.169.64.0/19, 89.169.128.0/17, 89.175.0.0/16, 89.178.0.0/15, 89.184.0.0/19, 89.184.224.0/19, 89.185.64.0/19, 89.186.224.0/19, 89.188.96.0/19, 89.188.160.0/19, 89.188.224.0/19, 89.189.0.0/19, 89.189.96.0/19, 89.189.128.0/18, 89.190.224.0/19, 89.191.224.0/21, 89.191.232.0/23, 89.191.235.0/24, 89.191.236.0/22, 89.191.240.0/20, 89.200.176.0/21, 89.204.0.0/17, 89.207.32.0/20, 89.207.64.0/20, 89.207.88.0/21, 89.207.96.0/21, 89.207.158.0/24, 89.207.216.0/21, 89.208.8.0/21, 89.208.16.0/20, 89.208.32.0/20, 89.208.48.0/22, 89.208.64.0/19, 89.208.96.0/20, 89.208.112.0/21, 89.208.120.0/23, 89.208.122.0/24, 89.208.124.0/22, 89.208.136.0/21, 89.208.144.0/20, 89.208.160.0/20, 89.208.176.0/22, 89.208.184.0/21, 89.208.192.0/19, 89.208.224.0/20, 89.221.16.0/20, 89.221.48.0/20, 89.221.192.0/20, 89.221.226.0/23, 89.221.228.0/22, 89.221.232.0/21, 89.222.128.0/17, 89.223.1.0/24, 89.223.5.0/24, 89.223.6.0/23, 89.223.8.0/23, 89.223.10.0/24, 89.223.12.0/23, 89.223.16.0/23, 89.223.20.0/23, 89.223.24.0/21, 89.223.32.0/19, 89.223.64.0/20, 89.223.80.0/21, 89.223.96.0/20, 89.223.120.0/21, 89.232.64.0/18, 89.232.128.0/19, 89.232.160.0/21, 89.232.168.0/22, 89.232.176.0/21, 89.232.188.0/22, 89.232.192.0/18, 89.235.128.0/18, 89.237.0.0/18, 89.239.128.0/18, 89.248.112.0/20, 89.248.176.0/20, 89.248.192.0/20, 89.248.224.0/23, 89.248.226.0/24, 89.248.228.0/22, 89.248.232.0/21, 89.249.16.0/20, 89.249.48.0/24, 89.249.50.0/24, 89.249.52.0/22, 89.249.112.0/20, 89.249.128.0/19, 89.249.160.0/20, 89.249.224.0/19, 89.250.0.0/19, 89.250.144.0/20, 89.250.160.0/20, 89.250.208.0/20, 89.250.224.0/20, 89.251.64.0/20, 89.251.96.0/20, 89.251.144.0/20, 89.251.160.0/20, 89.252.64.0/18, 89.253.0.0/18, 89.253.192.0/18, 89.254.192.0/18, 89.255.64.0/18, 90.150.0.0/15, 90.154.0.0/17, 90.155.128.0/17, 90.156.128.0/20, 90.156.148.0/22, 90.156.152.0/21, 90.156.168.0/21, 90.156.176.0/20, 90.156.200.0/21, 90.156.208.0/21, 90.156.216.0/22, 90.156.224.0/19, 90.157.0.0/17, 90.188.0.0/15, 91.76.0.0/14, 91.90.32.0/21, 91.90.198.0/23, 91.90.209.0/24, 91.90.210.0/23, 91.90.212.0/22, 91.90.220.0/23, 91.92.244.0/23, 91.102.72.0/21, 91.102.152.0/21, 91.102.200.0/21, 91.102.216.0/21, 91.103.72.0/21, 91.103.110.0/23, 91.103.140.0/24, 91.103.142.0/24, 91.103.192.0/20, 91.103.208.0/21, 91.103.224.0/21, 91.105.128.0/18, 91.105.194.0/23, 91.105.196.0/22, 91.105.232.0/21, 91.106.112.0/21, 91.106.200.0/21, 91.106.232.0/21, 91.107.0.0/18, 91.107.64.0/21, 91.107.80.0/21, 91.107.96.0/19, 91.108.168.0/21, 91.108.187.0/24, 91.109.64.0/19, 91.109.128.0/19, 91.109.200.0/21, 91.109.224.0/21, 91.122.0.0/16, 91.123.16.0/20, 91.123.80.0/20, 91.132.20.0/22, 91.132.96.0/22, 91.132.104.0/22, 91.132.188.0/22, 91.132.196.0/22, 91.132.216.0/22, 91.132.228.0/22, 91.133.0.0/19, 91.135.144.0/20, 91.135.208.0/20, 91.142.72.0/21, 91.142.80.0/20, 91.142.129.0/24, 91.142.135.0/24, 91.142.138.0/24, 91.142.141.0/24, 91.142.144.0/20, 91.143.0.0/18, 91.143.128.0/19, 91.143.160.0/20, 91.144.128.0/18, 91.146.0.0/18, 91.147.0.0/18, 91.149.64.0/18, 91.151.32.0/20, 91.151.176.0/20, 91.151.192.0/20, 91.184.224.0/20, 91.184.240.0/21, 91.184.254.0/23, 91.185.32.0/19, 91.185.64.0/19, 91.185.224.0/19, 91.186.96.0/19, 91.188.160.0/19, 91.188.196.0/22, 91.188.223.0/24, 91.188.245.0/24, 91.188.246.0/23, 91.189.48.0/21, 91.189.80.0/21, 91.189.112.0/21, 91.189.160.0/21, 91.189.232.0/21, 91.189.240.0/21, 91.190.64.0/20, 91.190.80.0/21, 91.190.112.0/20, 91.190.156.0/24, 91.190.176.0/21, 91.190.232.0/21, 91.191.224.0/19, 91.192.20.0/22, 91.192.60.0/22, 91.192.68.0/22, 91.192.72.0/22, 91.192.96.0/22, 91.192.148.0/22, 91.192.168.0/21, 91.192.188.0/22, 91.192.244.0/22, 91.193.25.0/24, 91.193.36.0/22, 91.193.88.0/23, 91.193.110.0/23, 91.193.140.0/22, 91.193.148.0/22, 91.193.168.0/22, 91.193.176.0/22, 91.193.212.0/22, 91.193.216.0/21, 91.193.224.0/21, 91.193.236.0/22, 91.193.240.0/22, 91.194.2.0/23, 91.194.12.0/23, 91.194.16.0/23, 91.194.58.0/23, 91.194.64.0/23, 91.194.112.0/23, 91.194.120.0/23, 91.194.162.0/23, 91.194.164.0/23, 91.194.172.0/22, 91.194.184.0/23, 91.194.190.0/23, 91.194.206.0/23, 91.194.212.0/23, 91.194.226.0/23, 91.194.238.0/23, 91.194.244.0/22, 91.194.252.0/23, 91.195.16.0/23, 91.195.20.0/23, 91.195.30.0/23, 91.195.60.0/23, 91.195.86.0/23, 91.195.100.0/23, 91.195.114.0/23, 91.195.124.0/22, 91.195.128.0/22, 91.195.136.0/23, 91.195.148.0/23, 91.195.170.0/23, 91.195.178.0/23, 91.195.204.0/23, 91.195.210.0/23, 91.195.230.0/23, 91.196.4.0/24, 91.196.32.0/22, 91.196.60.0/22, 91.196.72.0/22, 91.196.84.0/22, 91.196.236.0/22, 91.196.244.0/22, 91.196.248.0/21, 91.197.0.0/22, 91.197.8.0/22, 91.197.20.0/22, 91.197.52.0/23, 91.197.54.0/24, 91.197.57.0/24, 91.197.64.0/22, 91.197.76.0/22, 91.197.96.0/22, 91.197.104.0/22, 91.197.124.0/22, 91.197.132.0/22, 91.197.156.0/22, 91.197.160.0/22, 91.197.172.0/22, 91.197.176.0/21, 91.197.188.0/22, 91.197.192.0/22, 91.197.204.0/22, 91.197.212.0/22, 91.197.236.0/22, 91.198.14.0/24, 91.198.33.0/24, 91.198.39.0/24, 91.198.46.0/24, 91.198.54.0/24, 91.198.68.0/23, 91.198.71.0/24, 91.198.75.0/24, 91.198.85.0/24, 91.198.98.0/24, 91.198.104.0/24, 91.198.130.0/24, 91.198.149.0/24, 91.198.153.0/24, 91.198.155.0/24, 91.198.175.0/24, 91.198.184.0/24, 91.198.208.0/24, 91.198.210.0/23, 91.198.212.0/23, 91.198.215.0/24, 91.198.218.0/24, 91.198.221.0/24, 91.198.225.0/24, 91.198.231.0/24, 91.198.254.0/24, 91.199.4.0/24, 91.199.10.0/24, 91.199.16.0/24, 91.199.26.0/24, 91.199.34.0/24, 91.199.37.0/24, 91.199.46.0/24, 91.199.62.0/24, 91.199.67.0/24, 91.199.80.0/24, 91.199.114.0/23, 91.199.133.0/24, 91.199.143.0/24, 91.199.149.0/24, 91.199.153.0/24, 91.199.156.0/24, 91.199.171.0/24, 91.199.174.0/24, 91.199.178.0/24, 91.199.196.0/23, 91.199.199.0/24, 91.199.205.0/24, 91.199.224.0/24, 91.199.229.0/24, 91.199.230.0/24, 91.199.232.0/24, 91.199.244.0/24, 91.199.251.0/24, 91.200.28.0/22, 91.200.44.0/22, 91.200.62.0/24, 91.200.84.0/22, 91.200.148.0/22, 91.200.160.0/22, 91.200.224.0/21, 91.201.0.0/22, 91.201.8.0/23, 91.201.11.0/24, 91.201.24.0/24, 91.201.26.0/23, 91.201.40.0/22, 91.201.48.0/21, 91.201.72.0/22, 91.201.84.0/23, 91.201.86.0/24, 91.201.108.0/22, 91.201.116.0/22, 91.201.176.0/22, 91.201.184.0/22, 91.201.204.0/22, 91.201.208.0/22, 91.201.228.0/22, 91.201.236.0/22, 91.201.248.0/21, 91.202.8.0/21, 91.202.16.0/21, 91.202.24.0/22, 91.202.36.0/22, 91.202.44.0/22, 91.202.68.0/22, 91.202.76.0/22, 91.202.84.0/22, 91.202.92.0/22, 91.202.140.0/22, 91.202.148.0/22, 91.202.164.0/22, 91.202.180.0/22, 91.202.184.0/22, 91.202.196.0/22, 91.202.204.0/22, 91.202.216.0/21, 91.202.232.0/22, 91.202.252.0/22, 91.203.0.0/22, 91.203.8.0/22, 91.203.36.0/22, 91.203.44.0/22, 91.203.64.0/22, 91.203.80.0/21, 91.203.105.0/24, 91.203.124.0/22, 91.203.152.0/22, 91.203.168.0/22, 91.203.176.0/21, 91.203.188.0/22, 91.203.192.0/21, 91.203.208.0/22, 91.203.224.0/23, 91.203.226.0/24, 91.203.232.0/21, 91.203.240.0/22, 91.203.252.0/22, 91.204.16.0/21, 91.204.56.0/22, 91.204.68.0/22, 91.204.96.0/20, 91.204.128.0/22, 91.204.136.0/22, 91.204.144.0/21, 91.204.164.0/22, 91.204.176.0/21, 91.204.184.0/22, 91.204.200.0/22, 91.204.228.0/22, 91.204.232.0/22, 91.204.240.0/22, 91.204.252.0/22, 91.205.0.0/22, 91.205.8.0/22, 91.205.24.0/22, 91.205.44.0/22, 91.205.52.0/22, 91.205.84.0/22, 91.205.120.0/22, 91.205.128.0/22, 91.205.144.0/22, 91.205.160.0/21, 91.205.168.0/22, 91.205.176.0/21, 91.205.188.0/22, 91.205.200.0/22, 91.205.208.0/22, 91.205.224.0/22, 91.205.236.0/22, 91.205.240.0/22, 91.205.252.0/22, 91.206.12.0/22, 91.206.16.0/22, 91.206.22.0/23, 91.206.54.0/23, 91.206.56.0/23, 91.206.58.0/24, 91.206.62.0/23, 91.206.66.0/23, 91.206.72.0/23, 91.206.88.0/23, 91.206.100.0/23, 91.206.110.0/23, 91.206.116.0/23, 91.206.120.0/23, 91.206.126.0/23, 91.206.146.0/23, 91.206.148.0/23, 91.206.166.0/24, 91.206.196.0/23, 91.206.212.0/23, 91.206.216.0/23, 91.206.222.0/23, 91.206.240.0/22, 91.206.248.0/23, 91.207.4.0/22, 91.207.9.0/24, 91.207.24.0/22, 91.207.30.0/23, 91.207.40.0/22, 91.207.58.0/23, 91.207.71.0/24, 91.207.76.0/23, 91.207.78.0/24, 91.207.82.0/23, 91.207.84.0/22, 91.207.96.0/22, 91.207.106.0/23, 91.207.108.0/23, 91.207.114.0/23, 91.207.132.0/23, 91.207.136.0/23, 91.207.140.0/22, 91.207.146.0/23, 91.207.170.0/23, 91.207.180.0/23, 91.207.183.0/24, 91.207.224.0/23, 91.207.238.0/23, 91.207.246.0/23, 91.207.252.0/23, 91.208.20.0/24, 91.208.23.0/24, 91.208.29.0/24, 91.208.33.0/24, 91.208.35.0/24, 91.208.39.0/24, 91.208.42.0/23, 91.208.84.0/24, 91.208.119.0/24, 91.208.121.0/24, 91.208.126.0/24, 91.208.132.0/24, 91.208.134.0/24, 91.208.136.0/24, 91.208.149.0/24, 91.208.161.0/24, 91.208.166.0/24, 91.208.171.0/24, 91.208.183.0/24, 91.208.185.0/24, 91.208.187.0/24, 91.208.205.0/24, 91.208.210.0/24, 91.208.216.0/24, 91.208.232.0/24, 91.208.238.0/24, 91.208.248.0/23, 91.208.253.0/24, 91.208.254.0/24, 91.209.9.0/24, 91.209.17.0/24, 91.209.54.0/24, 91.209.59.0/24, 91.209.65.0/24, 91.209.67.0/24, 91.209.69.0/24, 91.209.72.0/24, 91.209.76.0/23, 91.209.80.0/24, 91.209.85.0/24, 91.209.91.0/24, 91.209.94.0/24, 91.209.119.0/24, 91.209.122.0/24, 91.209.124.0/24, 91.209.127.0/24, 91.209.128.0/23, 91.209.130.0/24, 91.209.135.0/24, 91.209.140.0/24, 91.209.147.0/24, 91.209.148.0/24, 91.209.208.0/24, 91.209.218.0/24, 91.209.233.0/24, 91.210.0.0/21, 91.210.24.0/22, 91.210.44.0/22, 91.210.52.0/22, 91.210.84.0/22, 91.210.92.0/22, 91.210.96.0/22, 91.210.104.0/22, 91.210.112.0/22, 91.210.132.0/22, 91.210.148.0/22, 91.210.152.0/24, 91.210.156.0/22, 91.210.160.0/22, 91.210.168.0/22, 91.210.176.0/22, 91.210.184.0/22, 91.210.192.0/20, 91.210.216.0/22, 91.210.228.0/22, 91.210.252.0/22, 91.211.2.0/24, 91.211.4.0/22, 91.211.16.0/21, 91.211.26.0/24, 91.211.28.0/22, 91.211.36.0/22, 91.211.44.0/22, 91.211.52.0/22, 91.211.56.0/21, 91.211.76.0/22, 91.211.80.0/22, 91.211.92.0/22, 91.211.104.0/22, 91.211.113.0/24, 91.211.115.0/24, 91.211.124.0/22, 91.211.128.0/22, 91.211.140.0/22, 91.211.148.0/22, 91.211.172.0/22, 91.211.180.0/22, 91.211.184.0/22, 91.211.192.0/22, 91.211.208.0/22, 91.211.216.0/22, 91.211.228.0/22, 91.211.236.0/22, 91.212.7.0/24, 91.212.47.0/24, 91.212.50.0/24, 91.212.57.0/24, 91.212.60.0/24, 91.212.64.0/24, 91.212.68.0/24, 91.212.81.0/24, 91.212.97.0/24, 91.212.118.0/24, 91.212.137.0/24, 91.212.146.0/24, 91.212.150.0/23, 91.212.158.0/24, 91.212.171.0/24, 91.212.179.0/24, 91.212.197.0/24, 91.212.198.0/24, 91.212.201.0/24, 91.212.217.0/24, 91.212.218.0/24, 91.212.220.0/24, 91.212.226.0/24, 91.212.239.0/24, 91.212.248.0/24, 91.213.5.0/24, 91.213.14.0/24, 91.213.23.0/24, 91.213.44.0/24, 91.213.48.0/24, 91.213.51.0/24, 91.213.67.0/24, 91.213.87.0/24, 91.213.104.0/24, 91.213.109.0/24, 91.213.113.0/24, 91.213.114.0/24, 91.213.117.0/24, 91.213.125.0/24, 91.213.130.0/24, 91.213.138.0/23, 91.213.141.0/24, 91.213.142.0/24, 91.213.144.0/24, 91.213.158.0/24, 91.213.163.0/24, 91.213.166.0/24, 91.213.168.0/23, 91.213.176.0/24, 91.213.184.0/24, 91.213.196.0/24, 91.213.213.0/24, 91.213.240.0/24, 91.213.243.0/24, 91.213.244.0/24, 91.213.248.0/23, 91.214.40.0/23, 91.214.48.0/22, 91.214.56.0/22, 91.214.68.0/22, 91.214.76.0/23, 91.214.80.0/22, 91.214.96.0/22, 91.214.116.0/24, 91.214.120.0/22, 91.214.128.0/22, 91.214.140.0/22, 91.214.144.0/22, 91.214.160.0/22, 91.214.172.0/24, 91.214.175.0/24, 91.214.184.0/22, 91.214.196.0/22, 91.214.204.0/22, 91.214.212.0/22, 91.214.220.0/22, 91.214.224.0/22, 91.214.240.0/22, 91.215.16.0/21, 91.215.28.0/22, 91.215.36.0/22, 91.215.40.0/22, 91.215.60.0/22, 91.215.76.0/22, 91.215.84.0/23, 91.215.88.0/22, 91.215.108.0/22, 91.215.112.0/22, 91.215.120.0/22, 91.215.128.0/22, 91.215.140.0/22, 91.215.168.0/21, 91.215.188.0/22, 91.215.192.0/20, 91.215.208.0/21, 91.215.220.0/22, 91.215.224.0/22, 91.215.232.0/22, 91.215.244.0/22, 91.215.248.0/21, 91.216.46.0/24, 91.216.48.0/23, 91.216.50.0/24, 91.216.53.0/24, 91.216.66.0/24, 91.216.68.0/23, 91.216.72.0/24, 91.216.81.0/24, 91.216.96.0/24, 91.216.114.0/24, 91.216.121.0/24, 91.216.123.0/24, 91.216.126.0/24, 91.216.129.0/24, 91.216.130.0/24, 91.216.147.0/24, 91.216.164.0/24, 91.216.167.0/24, 91.216.175.0/24, 91.216.186.0/23, 91.216.198.0/24, 91.216.205.0/24, 91.216.208.0/24, 91.216.211.0/24, 91.216.212.0/24, 91.216.240.0/24, 91.217.2.0/23, 91.217.4.0/23, 91.217.8.0/22, 91.217.16.0/23, 91.217.20.0/23, 91.217.34.0/23, 91.217.42.0/23, 91.217.46.0/23, 91.217.62.0/23, 91.217.70.0/23, 91.217.74.0/23, 91.217.78.0/23, 91.217.102.0/23, 91.217.108.0/22, 91.217.125.0/24, 91.217.129.0/24, 91.217.136.0/23, 91.217.143.0/24, 91.217.156.0/24, 91.217.163.0/24, 91.217.178.0/24, 91.217.180.0/24, 91.217.184.0/24, 91.217.190.0/24, 91.217.194.0/24, 91.217.196.0/24, 91.217.202.0/24, 91.217.220.0/24, 91.217.227.0/24, 91.217.233.0/24, 91.217.244.0/24, 91.218.8.0/22, 91.218.16.0/22, 91.218.24.0/21, 91.218.40.0/22, 91.218.84.0/22, 91.218.92.0/22, 91.218.100.0/22, 91.218.108.0/22, 91.218.112.0/21, 91.218.128.0/20, 91.218.144.0/22, 91.218.176.0/22, 91.218.220.0/22, 91.218.228.0/22, 91.218.244.0/22, 91.218.248.0/22, 91.219.4.0/22, 91.219.12.0/22, 91.219.23.0/24, 91.219.24.0/22, 91.219.32.0/21, 91.219.48.0/22, 91.219.56.0/22, 91.219.64.0/22, 91.219.72.0/22, 91.219.88.0/22, 91.219.96.0/21, 91.219.117.0/24, 91.219.124.0/22, 91.219.136.0/22, 91.219.148.0/22, 91.219.156.0/22, 91.219.160.0/21, 91.219.176.0/21, 91.219.188.0/22, 91.219.192.0/22, 91.219.200.0/21, 91.219.228.0/22, 91.219.248.0/22, 91.220.1.0/24, 91.220.5.0/24, 91.220.11.0/24, 91.220.12.0/24, 91.220.31.0/24, 91.220.33.0/24, 91.220.38.0/24, 91.220.46.0/24, 91.220.57.0/24, 91.220.69.0/24, 91.220.76.0/24, 91.220.87.0/24, 91.220.92.0/23, 91.220.109.0/24, 91.220.112.0/24, 91.220.120.0/24, 91.220.124.0/24, 91.220.133.0/24, 91.220.135.0/24, 91.220.137.0/24, 91.220.145.0/24, 91.220.146.0/24, 91.220.152.0/24, 91.220.155.0/24, 91.220.163.0/24, 91.220.166.0/24, 91.220.169.0/24, 91.220.170.0/24, 91.220.181.0/24, 91.220.186.0/24, 91.220.188.0/24, 91.220.211.0/24, 91.220.215.0/24, 91.220.218.0/23, 91.220.220.0/24, 91.220.233.0/24, 91.221.0.0/23, 91.221.16.0/22, 91.221.24.0/23, 91.221.28.0/22, 91.221.34.0/23, 91.221.36.0/23, 91.221.42.0/23, 91.221.48.0/23, 91.221.56.0/23, 91.221.68.0/24, 91.221.70.0/23, 91.221.90.0/23, 91.221.98.0/24, 91.221.102.0/23, 91.221.108.0/23, 91.221.132.0/23, 91.221.136.0/23, 91.221.140.0/23, 91.221.156.0/23, 91.221.162.0/23, 91.221.164.0/23, 91.221.174.0/23, 91.221.176.0/23, 91.221.180.0/23, 91.221.188.0/23, 91.221.194.0/23, 91.221.198.0/23, 91.221.200.0/23, 91.221.242.0/23, 91.221.246.0/23, 91.221.248.0/23, 91.221.252.0/23, 91.222.28.0/22, 91.222.120.0/22, 91.222.128.0/22, 91.222.156.0/22, 91.222.164.0/22, 91.222.208.0/22, 91.222.216.0/22, 91.222.228.0/22, 91.222.236.0/22, 91.222.240.0/22, 91.223.1.0/24, 91.223.15.0/24, 91.223.18.0/24, 91.223.25.0/24, 91.223.28.0/24, 91.223.32.0/24, 91.223.37.0/24, 91.223.44.0/24, 91.223.48.0/24, 91.223.63.0/24, 91.223.70.0/24, 91.223.75.0/24, 91.223.89.0/24, 91.223.90.0/24, 91.223.93.0/24, 91.223.98.0/24, 91.223.105.0/24, 91.223.108.0/23, 91.223.118.0/24, 91.223.120.0/24, 91.223.128.0/24, 91.223.137.0/24, 91.223.138.0/24, 91.223.144.0/24, 91.223.153.0/24, 91.223.170.0/24, 91.223.176.0/24, 91.223.181.0/24, 91.223.188.0/24, 91.223.217.0/24, 91.223.224.0/24, 91.223.226.0/24, 91.223.239.0/24, 91.223.242.0/24, 91.223.244.0/24, 91.223.246.0/24, 91.223.251.0/24, 91.223.252.0/24, 91.223.254.0/24, 91.224.14.0/23, 91.224.18.0/23, 91.224.30.0/23, 91.224.32.0/22, 91.224.52.0/23, 91.224.76.0/23, 91.224.80.0/22, 91.224.86.0/23, 91.224.98.0/23, 91.224.102.0/23, 91.224.106.0/23, 91.224.124.0/22, 91.224.130.0/23, 91.224.132.0/23, 91.224.136.0/22, 91.224.154.0/23, 91.224.166.0/23, 91.224.168.0/23, 91.224.182.0/23, 91.224.194.0/23, 91.224.204.0/22, 91.224.208.0/23, 91.224.218.0/23, 91.224.232.0/22, 91.224.254.0/23, 91.225.20.0/22, 91.225.24.0/23, 91.225.27.0/24, 91.225.40.0/22, 91.225.72.0/21, 91.225.88.0/22, 91.225.100.0/22, 91.225.112.0/22, 91.225.124.0/24, 91.225.127.0/24, 91.225.148.0/22, 91.225.192.0/22, 91.225.208.0/21, 91.225.216.0/24, 91.225.228.0/22, 91.225.236.0/22, 91.225.252.0/22, 91.226.10.0/23, 91.226.14.0/23, 91.226.30.0/23, 91.226.48.0/23, 91.226.60.0/22, 91.226.64.0/22, 91.226.73.0/24, 91.226.80.0/22, 91.226.92.0/22, 91.226.104.0/22, 91.226.116.0/24, 91.226.120.0/22, 91.226.128.0/21, 91.226.136.0/23, 91.226.140.0/22, 91.226.148.0/22, 91.226.152.0/22, 91.226.161.0/24, 91.226.164.0/22, 91.226.171.0/24, 91.226.172.0/22, 91.226.178.0/24, 91.226.185.0/24, 91.226.186.0/24, 91.226.188.0/23, 91.226.200.0/24, 91.226.204.0/23, 91.226.208.0/22, 91.226.222.0/23, 91.226.234.0/24, 91.226.250.0/24, 91.227.8.0/22, 91.227.12.0/23, 91.227.16.0/22, 91.227.23.0/24, 91.227.28.0/22, 91.227.32.0/24, 91.227.34.0/23, 91.227.44.0/22, 91.227.50.0/23, 91.227.52.0/23, 91.227.54.0/24, 91.227.68.0/24, 91.227.79.0/24, 91.227.91.0/24, 91.227.92.0/22, 91.227.112.0/23, 91.227.136.0/24, 91.227.140.0/22, 91.227.148.0/23, 91.227.152.0/23, 91.227.154.0/24, 91.227.160.0/22, 91.227.169.0/24, 91.227.172.0/22, 91.227.188.0/22, 91.227.195.0/24, 91.227.198.0/24, 91.227.210.0/23, 91.227.228.0/24, 91.227.242.0/23, 91.227.244.0/24, 91.228.26.0/23, 91.228.30.0/23, 91.228.40.0/23, 91.228.48.0/22, 91.228.54.0/23, 91.228.57.0/24, 91.228.62.0/23, 91.228.76.0/23, 91.228.96.0/22, 91.228.102.0/23, 91.228.116.0/23, 91.228.118.0/24, 91.228.120.0/23, 91.228.124.0/24, 91.228.148.0/24, 91.228.161.0/24, 91.228.177.0/24, 91.228.178.0/23, 91.228.180.0/22, 91.228.184.0/24, 91.228.205.0/24, 91.228.208.0/22, 91.228.220.0/22, 91.228.224.0/23, 91.228.226.0/24, 91.228.228.0/24, 91.228.230.0/24, 91.228.238.0/23, 91.228.246.0/23, 91.228.250.0/24, 91.229.2.0/24, 91.229.8.0/22, 91.229.48.0/24, 91.229.50.0/23, 91.229.59.0/24, 91.229.74.0/23, 91.229.90.0/23, 91.229.96.0/22, 91.229.100.0/24, 91.229.108.0/22, 91.229.112.0/23, 91.229.128.0/23, 91.229.146.0/24, 91.229.154.0/24, 91.229.176.0/23, 91.229.189.0/24, 91.229.190.0/23, 91.229.196.0/22, 91.229.200.0/24, 91.229.208.0/23, 91.229.211.0/24, 91.229.221.0/24, 91.229.233.0/24, 91.229.234.0/23, 91.229.237.0/24, 91.229.239.0/24, 91.230.10.0/23, 91.230.18.0/23, 91.230.20.0/23, 91.230.23.0/24, 91.230.26.0/24, 91.230.50.0/24, 91.230.52.0/23, 91.230.54.0/24, 91.230.60.0/22, 91.230.65.0/24, 91.230.72.0/24, 91.230.84.0/24, 91.230.91.0/24, 91.230.94.0/23, 91.230.100.0/23, 91.230.107.0/24, 91.230.133.0/24, 91.230.135.0/24, 91.230.136.0/22, 91.230.146.0/24, 91.230.150.0/24, 91.230.152.0/22, 91.230.182.0/23, 91.230.184.0/22, 91.230.191.0/24, 91.230.196.0/23, 91.230.208.0/22, 91.230.215.0/24, 91.230.218.0/24, 91.230.224.0/24, 91.230.226.0/23, 91.230.228.0/23, 91.230.251.0/24, 91.230.252.0/23, 91.231.8.0/24, 91.231.16.0/23, 91.231.94.0/23, 91.231.96.0/24, 91.231.112.0/22, 91.231.128.0/21, 91.231.136.0/23, 91.231.141.0/24, 91.231.145.0/24, 91.231.146.0/24, 91.231.161.0/24, 91.231.162.0/23, 91.231.176.0/23, 91.231.180.0/24, 91.231.196.0/22, 91.231.213.0/24, 91.231.214.0/23, 91.231.218.0/23, 91.231.233.0/24, 91.231.234.0/23, 91.231.236.0/22, 91.232.6.0/23, 91.232.8.0/21, 91.232.34.0/24, 91.232.39.0/24, 91.232.70.0/24, 91.232.80.0/24, 91.232.92.0/22, 91.232.108.0/22, 91.232.113.0/24, 91.232.116.0/22, 91.232.120.0/22, 91.232.126.0/24, 91.232.131.0/24, 91.232.132.0/22, 91.232.144.0/22, 91.232.162.0/24, 91.232.168.0/22, 91.232.172.0/23, 91.232.188.0/23, 91.232.196.0/23, 91.232.202.0/23, 91.232.204.0/22, 91.232.216.0/23, 91.232.225.0/24, 91.232.230.0/23, 91.232.243.0/24, 91.232.244.0/23, 91.232.246.0/24, 91.232.252.0/23, 91.233.4.0/24, 91.233.28.0/22, 91.233.40.0/21, 91.233.55.0/24, 91.233.63.0/24, 91.233.65.0/24, 91.233.82.0/24, 91.233.88.0/24, 91.233.92.0/22, 91.233.132.0/22, 91.233.144.0/22, 91.233.148.0/23, 91.233.164.0/22, 91.233.168.0/22, 91.233.172.0/23, 91.233.175.0/24, 91.233.188.0/23, 91.233.212.0/23, 91.233.216.0/21, 91.233.226.0/24, 91.233.230.0/23, 91.233.238.0/23, 91.233.240.0/22, 91.233.246.0/23, 91.234.11.0/24, 91.234.16.0/24, 91.234.48.0/24, 91.234.54.0/24, 91.234.60.0/22, 91.234.64.0/23, 91.234.68.0/22, 91.234.80.0/22, 91.234.96.0/22, 91.234.108.0/22, 91.234.116.0/22, 91.234.152.0/22, 91.234.164.0/22, 91.234.170.0/24, 91.234.172.0/22, 91.234.177.0/24, 91.234.178.0/24, 91.234.180.0/22, 91.234.188.0/24, 91.234.224.0/23, 91.235.7.0/24, 91.235.12.0/22, 91.235.26.0/23, 91.235.28.0/22, 91.235.32.0/23, 91.235.42.0/24, 91.235.66.0/24, 91.235.72.0/22, 91.235.80.0/23, 91.235.88.0/21, 91.235.101.0/24, 91.235.103.0/24, 91.235.106.0/24, 91.235.112.0/24, 91.235.127.0/24, 91.235.136.0/23, 91.235.144.0/22, 91.235.148.0/24, 91.235.156.0/23, 91.235.169.0/24, 91.235.175.0/24, 91.235.180.0/23, 91.235.184.0/21, 91.235.196.0/22, 91.235.203.0/24, 91.235.213.0/24, 91.235.220.0/23, 91.235.233.0/24, 91.235.241.0/24, 91.235.244.0/22, 91.236.2.0/23, 91.236.10.0/23, 91.236.12.0/24, 91.236.14.0/23, 91.236.22.0/23, 91.236.24.0/24, 91.236.29.0/24, 91.236.30.0/23, 91.236.48.0/22, 91.236.60.0/22, 91.236.80.0/23, 91.236.92.0/22, 91.236.100.0/22, 91.236.104.0/24, 91.236.108.0/22, 91.236.112.0/22, 91.236.118.0/23, 91.236.124.0/24, 91.236.134.0/23, 91.236.136.0/24, 91.236.140.0/22, 91.236.148.0/22, 91.236.164.0/22, 91.236.171.0/24, 91.236.176.0/22, 91.236.180.0/23, 91.236.196.0/22, 91.236.200.0/24, 91.236.204.0/23, 91.236.213.0/24, 91.236.234.0/23, 91.236.238.0/24, 91.236.246.0/23, 91.236.252.0/23, 91.237.0.0/22, 91.237.20.0/24, 91.237.32.0/24, 91.237.37.0/24, 91.237.53.0/24, 91.237.54.0/23, 91.237.76.0/24, 91.237.82.0/23, 91.237.94.0/24, 91.237.97.0/24, 91.237.144.0/22, 91.237.148.0/24, 91.237.163.0/24, 91.237.178.0/23, 91.237.181.0/24, 91.237.182.0/23, 91.237.186.0/23, 91.237.188.0/23, 91.237.192.0/24, 91.237.199.0/24, 91.237.200.0/22, 91.237.209.0/24, 91.237.213.0/24, 91.237.220.0/22, 91.237.249.0/24, 91.237.252.0/23, 91.238.2.0/23, 91.238.10.0/23, 91.238.24.0/22, 91.238.28.0/23, 91.238.34.0/23, 91.238.44.0/22, 91.238.48.0/23, 91.238.64.0/22, 91.238.68.0/23, 91.238.74.0/23, 91.238.79.0/24, 91.238.80.0/23, 91.238.86.0/23, 91.238.88.0/22, 91.238.96.0/22, 91.238.108.0/22, 91.238.115.0/24, 91.238.120.0/23, 91.238.122.0/24, 91.238.127.0/24, 91.238.128.0/23, 91.238.133.0/24, 91.238.136.0/22, 91.238.140.0/24, 91.238.156.0/22, 91.238.168.0/22, 91.238.182.0/23, 91.238.184.0/23, 91.238.188.0/22, 91.238.200.0/23, 91.238.202.0/24, 91.238.210.0/23, 91.238.222.0/23, 91.238.224.0/24, 91.238.227.0/24, 91.238.228.0/22, 91.238.244.0/23, 91.238.248.0/23, 91.238.252.0/23, 91.239.4.0/23, 91.239.16.0/22, 91.239.23.0/24, 91.239.26.0/23, 91.239.40.0/23, 91.239.42.0/24, 91.239.44.0/23, 91.239.60.0/24, 91.239.63.0/24, 91.239.68.0/22, 91.239.88.0/23, 91.239.94.0/24, 91.239.98.0/23, 91.239.102.0/23, 91.239.112.0/24, 91.239.121.0/24, 91.239.126.0/23, 91.239.131.0/24, 91.239.132.0/22, 91.239.140.0/22, 91.239.158.0/23, 91.239.172.0/24, 91.239.176.0/24, 91.239.184.0/24, 91.239.190.0/23, 91.239.215.0/24, 91.239.228.0/23, 91.239.239.0/24, 91.240.16.0/24, 91.240.25.0/24, 91.240.36.0/24, 91.240.38.0/23, 91.240.45.0/24, 91.240.71.0/24, 91.240.74.0/23, 91.240.84.0/22, 91.240.90.0/23, 91.240.102.0/23, 91.240.110.0/23, 91.240.112.0/22, 91.240.119.0/24, 91.240.120.0/21, 91.240.144.0/24, 91.240.162.0/24, 91.240.168.0/22, 91.240.172.0/24, 91.240.179.0/24, 91.240.190.0/23, 91.240.208.0/22, 91.240.218.0/23, 91.240.220.0/24, 91.240.222.0/24, 91.240.225.0/24, 91.240.242.0/23, 91.241.4.0/24, 91.241.12.0/23, 91.241.16.0/22, 91.241.22.0/24, 91.241.43.0/24, 91.241.45.0/24, 91.241.46.0/23, 91.241.54.0/24, 91.241.58.0/24, 91.241.68.0/23, 91.241.84.0/23, 91.241.88.0/22, 91.241.128.0/19, 91.241.160.0/21, 91.241.182.0/23, 91.241.184.0/21, 91.242.32.0/21, 91.242.40.0/22, 91.242.161.0/24, 91.242.164.0/22, 91.242.168.0/22, 91.242.176.0/20, 91.242.208.0/22, 91.242.213.0/24, 91.242.255.0/24, 91.243.32.0/19, 91.243.80.0/24, 91.243.82.0/23, 91.243.84.0/24, 91.243.86.0/24, 91.243.88.0/21, 91.243.96.0/20, 91.243.112.0/23, 91.243.120.0/23, 91.243.124.0/23, 91.243.190.0/23, 91.243.224.0/19, 91.244.64.0/22, 91.244.72.0/21, 91.244.80.0/20, 91.244.112.0/22, 91.244.160.0/20, 91.244.176.0/22, 91.244.183.0/24, 91.244.232.0/22, 91.244.240.0/22, 91.244.244.0/24, 91.244.252.0/22, 91.245.32.0/19, 91.245.64.0/21, 91.245.128.0/19, 91.245.210.0/23, 91.246.0.0/22, 91.246.16.0/21, 91.246.24.0/23, 91.246.112.0/20, 91.246.160.0/21, 91.246.219.0/24, 91.247.0.0/19, 91.247.32.0/22, 91.247.68.0/22, 91.247.96.0/19, 91.247.128.0/20, 91.247.148.0/22, 91.247.152.0/21, 91.247.190.0/24, 91.247.192.0/20, 91.247.208.0/21, 91.247.216.0/22, 91.247.220.0/24, 91.247.222.0/23, 91.247.232.0/21, 91.247.248.0/22, 91.247.254.0/23, 92.36.0.0/17, 92.37.128.0/17, 92.38.128.0/22, 92.38.136.0/22, 92.38.152.0/23, 92.38.156.0/24, 92.38.188.0/22, 92.38.192.0/20, 92.38.212.0/22, 92.38.216.0/21, 92.38.224.0/19, 92.39.64.0/20, 92.39.96.0/20, 92.39.128.0/20, 92.39.208.0/20, 92.39.224.0/20, 92.42.6.0/24, 92.42.8.0/21, 92.42.24.0/21, 92.42.40.0/22, 92.42.88.0/21, 92.42.96.0/24, 92.42.128.0/21, 92.42.160.0/21, 92.42.208.0/21, 92.43.0.0/21, 92.43.152.0/21, 92.43.166.0/23, 92.43.184.0/21, 92.49.128.0/18, 92.50.128.0/17, 92.51.0.0/19, 92.51.32.0/20, 92.51.48.0/21, 92.53.64.0/18, 92.53.224.0/21, 92.54.64.0/18, 92.55.0.0/18, 92.60.80.0/20, 92.61.16.0/20, 92.61.64.0/22, 92.61.68.0/23, 92.62.112.0/22, 92.62.116.0/24, 92.62.144.0/20, 92.63.64.0/20, 92.63.96.0/20, 92.63.184.0/22, 92.63.192.0/21, 92.63.200.0/22, 92.100.0.0/15, 92.118.64.0/22, 92.118.84.0/22, 92.118.88.0/21, 92.118.112.0/21, 92.118.124.0/22, 92.118.132.0/22, 92.118.140.0/22, 92.118.168.0/22, 92.118.196.0/22, 92.118.200.0/22, 92.119.200.0/22, 92.119.216.0/22, 92.124.0.0/14, 92.223.4.0/23, 92.223.6.0/24, 92.223.8.0/23, 92.223.14.0/24, 92.223.32.0/22, 92.223.36.0/24, 92.223.38.0/24, 92.223.41.0/24, 92.223.43.0/24, 92.223.49.0/24, 92.223.60.0/24, 92.223.64.0/23, 92.223.67.0/24, 92.223.72.0/24, 92.223.80.0/24, 92.223.87.0/24, 92.223.91.0/24, 92.223.103.0/24, 92.223.106.0/24, 92.223.108.0/22, 92.223.114.0/23, 92.223.122.0/23, 92.240.128.0/20, 92.240.192.0/19, 92.241.0.0/19, 92.241.96.0/19, 92.241.160.0/19, 92.241.224.0/19, 92.242.0.0/17, 92.243.96.0/19, 92.243.160.0/19, 92.244.224.0/19, 92.245.32.0/19, 92.246.148.0/22, 92.246.152.0/22, 92.246.160.0/19, 92.246.192.0/19, 92.248.128.0/17, 92.249.0.0/22, 92.249.4.0/24, 92.252.128.0/17, 92.255.1.0/24, 92.255.2.0/23, 92.255.4.0/22, 92.255.8.0/21, 92.255.16.0/20, 92.255.40.0/21, 92.255.48.0/22, 92.255.53.0/24, 92.255.54.0/23, 92.255.58.0/23, 92.255.60.0/22, 92.255.65.0/24, 92.255.66.0/23, 92.255.72.0/21, 92.255.88.0/21, 92.255.96.0/19, 92.255.128.0/17, 93.77.160.0/19, 93.80.0.0/15, 93.88.0.0/20, 93.88.76.0/22, 93.88.128.0/20, 93.88.160.0/19, 93.88.208.0/20, 93.89.176.0/20, 93.90.32.0/20, 93.90.80.0/20, 93.90.96.0/20, 93.90.208.0/20, 93.90.224.0/19, 93.91.0.0/20, 93.91.112.0/20, 93.91.160.0/20, 93.91.224.0/20, 93.92.32.0/21, 93.92.64.0/21, 93.92.80.0/20, 93.92.136.0/21, 93.92.176.0/21, 93.92.192.0/20, 93.92.216.0/21, 93.93.88.0/21, 93.93.136.0/21, 93.93.172.0/22, 93.94.144.0/20, 93.94.176.0/21, 93.94.232.0/21, 93.95.29.0/24, 93.95.96.0/21, 93.95.136.0/21, 93.95.152.0/21, 93.95.160.0/20, 93.95.192.0/21, 93.100.0.0/16, 93.114.142.0/24, 93.120.128.0/17, 93.123.128.0/17, 93.124.0.0/17, 93.153.128.0/17, 93.157.8.0/22, 93.157.16.0/21, 93.157.40.0/21, 93.157.56.0/23, 93.157.59.0/24, 93.157.60.0/22, 93.157.106.0/24, 93.157.120.0/21, 93.157.144.0/21, 93.157.160.0/20, 93.157.186.0/24, 93.157.202.0/23, 93.157.205.0/24, 93.157.206.0/24, 93.157.208.0/21, 93.157.232.0/21, 93.157.240.0/20, 93.158.128.0/18, 93.158.192.0/21, 93.158.224.0/21, 93.158.240.0/20, 93.159.216.0/21, 93.159.232.0/21, 93.159.240.0/21, 93.174.48.0/20, 93.174.72.0/21, 93.174.112.0/21, 93.174.128.0/21, 93.174.224.0/21, 93.174.240.0/21, 93.175.0.0/19, 93.177.0.0/18, 93.177.116.0/22, 93.177.126.0/24, 93.178.64.0/18, 93.179.104.0/21, 93.180.0.0/18, 93.180.136.0/21, 93.181.192.0/18, 93.182.0.0/18, 93.183.64.0/20, 93.183.80.0/22, 93.183.92.0/22, 93.183.104.0/21, 93.183.112.0/21, 93.184.160.0/20, 93.185.16.0/20, 93.185.164.0/22, 93.185.172.0/22, 93.185.176.0/20, 93.185.192.0/20, 93.186.48.0/20, 93.186.224.0/20, 93.187.72.0/21, 93.187.96.0/21, 93.187.120.0/21, 93.187.152.0/21, 93.187.176.0/20, 93.188.16.0/21, 93.188.40.0/21, 93.188.120.0/21, 93.188.152.0/23, 93.188.154.0/24, 93.188.156.0/22, 93.188.184.0/21, 93.188.200.0/21, 93.188.208.0/21, 93.189.8.0/21, 93.189.16.0/21, 93.189.40.0/21, 93.189.48.0/21, 93.189.72.0/21, 93.189.80.0/21, 93.189.144.0/21, 93.189.200.0/21, 93.189.216.0/21, 93.189.226.0/23, 93.189.228.0/22, 93.189.248.0/21, 93.190.16.0/21, 93.190.104.0/21, 93.190.176.0/21, 93.190.200.0/21, 93.190.224.0/21, 93.191.8.0/21, 93.191.16.0/21, 93.191.56.0/21, 93.191.72.0/21, 94.19.0.0/16, 94.24.128.0/17, 94.25.0.0/16, 94.26.128.0/18, 94.26.224.0/19, 94.28.0.0/16, 94.29.0.0/17, 94.31.128.0/17, 94.41.0.0/16, 94.45.0.0/19, 94.45.192.0/19, 94.50.0.0/15, 94.72.0.0/18, 94.73.192.0/18, 94.75.0.0/18, 94.75.128.0/18, 94.77.0.0/17, 94.77.128.0/18, 94.78.192.0/18, 94.79.0.0/18, 94.100.80.0/20, 94.100.176.0/20, 94.100.192.0/20, 94.101.64.0/20, 94.101.96.0/23, 94.101.105.0/24, 94.101.110.0/24, 94.102.16.0/20, 94.102.88.0/23, 94.102.112.0/20, 94.102.176.0/20, 94.103.0.0/22, 94.103.83.0/24, 94.103.84.0/22, 94.103.88.0/22, 94.103.92.0/23, 94.103.188.0/24, 94.103.224.0/20, 94.124.114.0/24, 94.124.168.0/21, 94.124.192.0/20, 94.124.224.0/21, 94.124.240.0/21, 94.125.0.0/20, 94.125.40.0/21, 94.125.48.0/21, 94.125.88.0/21, 94.125.184.0/21, 94.125.240.0/21, 94.126.24.0/21, 94.126.88.0/21, 94.126.152.0/21, 94.126.200.0/24, 94.126.202.0/23, 94.126.204.0/22, 94.127.88.0/21, 94.127.136.0/21, 94.127.144.0/20, 94.127.168.0/21, 94.127.176.0/21, 94.127.200.0/21, 94.127.216.0/21, 94.127.240.0/20, 94.131.23.0/24, 94.136.192.0/19, 94.137.0.0/18, 94.137.64.0/19, 94.137.192.0/18, 94.138.0.0/19, 94.138.128.0/19, 94.139.96.0/19, 94.139.240.0/20, 94.140.128.0/19, 94.140.192.0/18, 94.141.32.0/19, 94.141.100.0/22, 94.141.104.0/21, 94.141.112.0/20, 94.141.160.0/19, 94.141.244.0/22, 94.141.252.0/22, 94.142.0.0/19, 94.142.128.0/24, 94.142.248.0/24, 94.143.32.0/21, 94.143.48.0/20, 94.143.128.0/21, 94.143.144.0/21, 94.143.160.0/21, 94.143.240.0/21, 94.154.11.0/24, 94.154.64.0/19, 94.158.0.0/20, 94.158.16.0/22, 94.158.22.0/23, 94.158.32.0/20, 94.158.96.0/19, 94.158.160.0/20, 94.158.192.0/20, 94.158.216.0/24, 94.158.220.0/23, 94.158.222.0/24, 94.159.0.0/17, 94.180.0.0/15, 94.187.192.0/21, 94.187.200.0/22, 94.188.0.0/17, 94.190.0.0/17, 94.198.0.0/21, 94.198.16.0/21, 94.198.32.0/21, 94.198.50.0/23, 94.198.52.0/22, 94.198.104.0/21, 94.198.128.0/21, 94.198.192.0/21, 94.198.216.0/21, 94.198.232.0/21, 94.199.4.0/24, 94.199.64.0/20, 94.199.104.0/21, 94.199.216.0/21, 94.228.112.0/20, 94.228.160.0/20, 94.228.192.0/20, 94.228.240.0/20, 94.229.16.0/20, 94.229.224.0/19, 94.230.0.0/20, 94.230.32.0/20, 94.230.112.0/20, 94.230.128.0/20, 94.230.160.0/20, 94.230.240.0/20, 94.231.16.0/20, 94.231.112.0/20, 94.231.128.0/19, 94.231.160.0/20, 94.231.212.0/24, 94.231.216.0/21, 94.232.0.0/20, 94.232.16.0/21, 94.232.31.0/24, 94.232.48.0/20, 94.232.64.0/21, 94.232.88.0/21, 94.232.104.0/21, 94.232.128.0/20, 94.232.184.0/21, 94.232.200.0/21, 94.232.232.0/21, 94.233.0.0/16, 94.237.128.0/17, 94.240.64.0/18, 94.241.0.0/18, 94.241.192.0/18, 94.242.0.0/19, 94.242.32.0/20, 94.242.128.0/18, 94.243.0.0/18, 94.243.128.0/17, 94.245.128.0/18, 94.247.16.0/21, 94.247.56.0/21, 94.247.104.0/21, 94.247.120.0/21, 94.247.137.0/24, 94.247.140.0/24, 94.247.208.0/22, 94.247.212.0/24, 94.247.240.0/20, 94.250.248.0/21, 94.251.0.0/17, 94.253.0.0/17, 94.255.0.0/17, 95.24.0.0/13, 95.32.0.0/16, 95.37.0.0/16, 95.52.0.0/14, 95.64.128.0/17, 95.66.128.0/17, 95.67.128.0/17, 95.68.128.0/17, 95.70.0.0/17, 95.71.0.0/16, 95.72.0.0/15, 95.78.0.0/15, 95.80.64.0/18, 95.81.72.0/22, 95.81.88.0/22, 95.81.96.0/21, 95.81.192.0/18, 95.82.192.0/18, 95.83.0.0/17, 95.83.128.0/18, 95.84.0.0/18, 95.84.128.0/17, 95.85.88.0/24, 95.86.192.0/18, 95.104.128.0/17, 95.105.0.0/17, 95.106.0.0/16, 95.107.0.0/17, 95.108.128.0/17, 95.110.0.0/17, 95.128.136.0/21, 95.128.160.0/21, 95.128.176.0/21, 95.128.224.0/21, 95.128.240.0/21, 95.129.56.0/21, 95.129.72.0/21, 95.129.88.0/21, 95.129.136.0/21, 95.129.144.0/21, 95.129.160.0/20, 95.129.176.0/21, 95.129.232.0/21, 95.130.128.0/21, 95.130.176.0/21, 95.130.229.0/24, 95.130.230.0/24, 95.131.0.0/20, 95.131.24.0/21, 95.131.72.0/21, 95.131.88.0/21, 95.131.144.0/21, 95.131.176.0/21, 95.131.204.0/24, 95.131.208.0/21, 95.131.224.0/21, 95.137.0.0/17, 95.138.224.0/19, 95.139.0.0/16, 95.140.16.0/20, 95.140.144.0/20, 95.141.0.0/20, 95.141.176.0/20, 95.141.192.0/20, 95.141.224.0/20, 95.142.32.0/20, 95.142.192.0/20, 95.143.0.0/19, 95.143.105.0/24, 95.143.108.0/24, 95.143.110.0/24, 95.143.112.0/20, 95.143.176.0/20, 95.143.208.0/20, 95.152.0.0/18, 95.153.128.0/17, 95.154.64.0/18, 95.154.128.0/18, 95.156.64.0/18, 95.156.251.0/24, 95.158.192.0/18, 95.159.128.0/18, 95.161.140.0/22, 95.161.144.0/20, 95.161.160.0/20, 95.161.176.0/21, 95.161.188.0/22, 95.161.192.0/22, 95.161.200.0/21, 95.161.208.0/20, 95.161.228.0/22, 95.161.232.0/21, 95.163.0.0/17, 95.163.128.0/20, 95.163.144.0/24, 95.163.146.0/23, 95.163.152.0/21, 95.163.176.0/20, 95.163.208.0/20, 95.163.224.0/19, 95.165.0.0/16, 95.167.0.0/16, 95.169.96.0/19, 95.169.128.0/19, 95.170.96.0/19, 95.170.128.0/18, 95.171.0.0/19, 95.171.224.0/19, 95.172.32.0/19, 95.172.96.0/19, 95.172.128.0/19, 95.173.128.0/19, 95.174.88.0/21, 95.174.192.0/19, 95.175.224.0/19, 95.179.0.0/17, 95.181.0.0/17, 95.181.128.0/21, 95.181.148.0/22, 95.181.152.0/21, 95.181.160.0/20, 95.181.176.0/21, 95.181.184.0/22, 95.181.192.0/20, 95.181.224.0/21, 95.181.248.0/21, 95.182.32.0/19, 95.183.8.0/21, 95.183.16.0/20, 95.183.32.0/20, 95.183.64.0/18, 95.188.0.0/14, 95.213.0.0/16, 95.214.56.0/21, 95.214.116.0/22, 95.215.0.0/22, 95.215.12.0/22, 95.215.48.0/22, 95.215.68.0/22, 95.215.84.0/22, 95.215.96.0/21, 95.215.108.0/22, 95.215.112.0/21, 95.215.124.0/22, 95.215.148.0/22, 95.215.152.0/22, 95.215.176.0/21, 95.215.208.0/22, 95.215.228.0/22, 95.215.238.0/23, 95.215.240.0/22, 95.215.248.0/21, 95.220.0.0/15, 103.58.41.0/24, 103.76.52.0/22, 103.228.2.0/24, 109.60.128.0/17, 109.61.128.0/17, 109.62.128.0/17, 109.63.128.0/17, 109.68.16.0/21, 109.68.72.0/21, 109.68.112.0/21, 109.68.136.0/21, 109.68.168.0/21, 109.68.184.0/21, 109.68.200.0/21, 109.68.208.0/21, 109.69.16.0/20, 109.69.72.0/21, 109.70.24.0/21, 109.70.75.0/24, 109.70.184.0/21, 109.70.248.0/21, 109.71.8.0/21, 109.71.158.0/24, 109.71.176.0/21, 109.71.192.0/20, 109.71.240.0/21, 109.72.64.0/20, 109.72.128.0/20, 109.72.224.0/19, 109.73.0.0/20, 109.73.32.0/20, 109.73.192.0/20, 109.74.112.0/20, 109.74.128.0/20, 109.74.160.0/20, 109.74.208.0/20, 109.75.128.0/20, 109.75.192.0/20, 109.75.240.0/20, 109.94.0.0/19, 109.94.64.0/19, 109.94.176.0/20, 109.94.192.0/20, 109.94.211.0/24, 109.94.220.0/22, 109.95.16.0/21, 109.95.72.0/21, 109.95.80.0/21, 109.95.96.0/20, 109.95.120.0/22, 109.95.160.0/21, 109.95.192.0/21, 109.95.208.0/20, 109.95.224.0/21, 109.95.248.0/21, 109.105.64.0/19, 109.105.160.0/19, 109.106.20.0/22, 109.106.128.0/19, 109.106.192.0/19, 109.107.146.0/24, 109.107.160.0/22, 109.107.168.0/22, 109.107.180.0/22, 109.107.188.0/22, 109.108.32.0/19, 109.109.208.0/20, 109.110.32.0/19, 109.110.64.0/19, 109.111.0.0/19, 109.111.64.0/19, 109.111.128.0/18, 109.120.0.0/18, 109.120.160.0/20, 109.120.180.0/22, 109.120.188.0/22, 109.122.192.0/21, 109.123.128.0/18, 109.124.0.0/17, 109.124.192.0/18, 109.126.0.0/18, 109.126.192.0/18, 109.127.128.0/18, 109.161.0.0/17, 109.163.216.0/21, 109.163.240.0/21, 109.165.0.0/17, 109.167.128.0/17, 109.168.128.0/17, 109.169.128.0/17, 109.170.0.0/17, 109.171.0.0/17, 109.172.0.0/21, 109.172.10.0/23, 109.172.12.0/22, 109.172.16.0/20, 109.172.32.0/19, 109.172.64.0/20, 109.172.80.0/21, 109.172.88.0/22, 109.172.94.0/23, 109.172.96.0/19, 109.173.0.0/17, 109.174.0.0/17, 109.184.0.0/16, 109.187.0.0/16, 109.188.0.0/16, 109.191.0.0/16, 109.194.0.0/15, 109.196.16.0/20, 109.196.64.0/20, 109.196.165.0/24, 109.196.167.0/24, 109.196.192.0/19, 109.197.8.0/21, 109.197.16.0/20, 109.197.48.0/21, 109.197.72.0/21, 109.197.88.0/21, 109.197.112.0/20, 109.197.128.0/19, 109.197.162.0/23, 109.197.192.0/20, 109.197.224.0/21, 109.197.248.0/21, 109.198.64.0/18, 109.198.160.0/19, 109.198.192.0/18, 109.200.96.0/19, 109.200.128.0/19, 109.201.64.0/18, 109.201.192.0/19, 109.202.0.0/18, 109.202.160.0/19, 109.203.192.0/19, 109.205.57.0/24, 109.205.80.0/21, 109.205.144.0/21, 109.205.248.0/21, 109.206.128.0/19, 109.207.0.0/20, 109.207.80.0/20, 109.207.160.0/21, 109.207.176.0/20, 109.207.224.0/20, 109.225.0.0/18, 109.225.192.0/18, 109.226.64.0/18, 109.226.192.0/18, 109.227.192.0/18, 109.229.32.0/19, 109.229.64.0/18, 109.229.128.0/19, 109.229.224.0/19, 109.230.128.0/18, 109.232.104.0/21, 109.232.184.0/21, 109.232.248.0/21, 109.233.168.0/21, 109.233.200.0/22, 109.233.208.0/21, 109.233.224.0/20, 109.233.240.0/21, 109.234.8.0/21, 109.234.24.0/21, 109.234.32.0/24, 109.234.128.0/21, 109.234.152.0/21, 109.235.24.0/21, 109.235.88.0/21, 109.235.160.0/21, 109.235.184.0/21, 109.235.208.0/20, 109.236.52.0/22, 109.236.56.0/22, 109.236.64.0/20, 109.236.96.0/20, 109.236.208.0/20, 109.236.224.0/19, 109.237.0.0/20, 109.237.96.0/20, 109.237.144.0/20, 109.237.224.0/20, 109.238.80.0/21, 109.238.88.0/22, 109.238.92.0/23, 109.238.96.0/20, 109.238.192.0/20, 109.238.240.0/20, 109.239.208.0/20, 109.252.0.0/16, 109.254.0.0/16, 111.88.78.0/24, 111.88.96.0/19, 111.88.128.0/21, 111.88.144.0/20, 111.88.160.0/21, 111.88.172.0/22, 111.88.176.0/21, 111.88.192.0/19, 111.88.224.0/21, 111.88.240.0/20, 128.0.24.0/21, 128.0.66.0/23, 128.0.80.0/20, 128.0.106.0/24, 128.0.128.0/20, 128.0.160.0/21, 128.65.136.0/21, 128.68.0.0/14, 128.72.0.0/14, 128.140.160.0/20, 128.140.192.0/21, 128.204.0.0/17, 128.204.160.0/19, 129.101.64.0/19, 130.0.216.0/21, 130.49.128.0/23, 130.49.132.0/22, 130.49.136.0/21, 130.49.144.0/21, 130.49.152.0/23, 130.49.154.0/24, 130.49.156.0/22, 130.49.164.0/22, 130.49.172.0/22, 130.49.176.0/20, 130.49.192.0/18, 130.193.32.0/19, 130.193.64.0/21, 130.193.73.0/24, 130.255.8.0/21, 130.255.32.0/19, 130.255.80.0/21, 130.255.128.0/20, 131.117.216.0/21, 131.222.134.0/23, 131.222.202.0/24, 132.243.176.0/21, 132.243.184.0/24, 132.243.190.0/23, 134.0.96.0/20, 134.0.112.0/21, 134.19.128.0/21, 134.19.144.0/20, 134.90.152.0/21, 134.90.160.0/19, 134.90.208.0/21, 134.255.128.0/19, 135.106.128.0/22, 135.106.132.0/23, 135.106.136.0/21, 135.106.144.0/20, 135.106.160.0/19, 135.106.192.0/18, 136.169.128.0/17, 136.234.0.0/18, 136.234.64.0/19, 136.234.96.0/20, 136.234.112.0/21, 136.234.120.0/22, 136.234.124.0/23, 136.234.126.0/24, 137.22.144.0/20, 138.16.128.0/21, 138.16.136.0/23, 138.16.140.0/22, 138.16.144.0/20, 138.16.160.0/19, 138.16.192.0/18, 139.28.52.0/22, 139.28.72.0/22, 139.28.168.0/22, 139.28.180.0/22, 139.45.224.0/19, 139.100.160.0/20, 139.100.192.0/18, 141.0.176.0/21, 141.8.128.0/18, 141.8.192.0/21, 141.98.28.0/22, 141.98.84.0/22, 141.98.168.0/22, 141.98.232.0/22, 141.101.151.0/24, 141.101.152.0/24, 141.101.201.0/24, 141.101.204.0/24, 141.101.228.0/24, 141.101.231.0/24, 141.105.24.0/21, 141.105.32.0/20, 141.105.48.0/21, 141.105.64.0/21, 141.133.128.0/22, 141.133.136.0/22, 141.133.176.0/20, 141.133.192.0/18, 141.136.112.0/21, 144.206.0.0/16, 145.249.0.0/16, 145.255.0.0/19, 145.255.32.0/20, 145.255.136.0/21, 145.255.232.0/21, 145.255.248.0/21, 146.19.13.0/24, 146.19.27.0/24, 146.19.58.0/23, 146.19.73.0/24, 146.19.78.0/24, 146.19.84.0/24, 146.19.91.0/24, 146.19.98.0/24, 146.19.115.0/24, 146.19.124.0/24, 146.19.142.0/24, 146.19.209.0/24, 146.19.211.0/24, 146.19.213.0/24, 146.19.233.0/24, 146.19.234.0/24, 146.19.242.0/24, 146.19.244.0/24, 146.19.253.0/24, 146.66.8.0/21, 146.66.160.0/19, 146.66.192.0/20, 146.185.192.0/20, 146.185.208.0/22, 146.185.212.0/23, 146.185.215.0/24, 146.185.218.0/24, 146.185.222.0/23, 146.185.224.0/21, 146.185.232.0/22, 146.185.238.0/23, 146.185.240.0/21, 146.185.252.0/22, 146.247.0.0/21, 146.247.32.0/21, 146.255.188.0/24, 146.255.192.0/19, 147.45.0.0/16, 147.78.8.0/22, 147.78.180.0/22, 147.78.188.0/22, 149.62.0.0/19, 149.62.48.0/21, 149.62.160.0/21, 149.126.16.0/21, 149.126.96.0/21, 149.126.168.0/21, 149.154.64.0/21, 149.154.104.0/21, 149.154.112.0/21, 149.154.128.0/20, 149.255.0.0/19, 149.255.112.0/21, 149.255.132.0/22, 149.255.160.0/21, 151.0.0.0/18, 151.236.64.0/18, 151.237.168.0/21, 151.237.240.0/21, 151.248.112.0/20, 151.252.64.0/19, 151.252.104.0/21, 152.89.108.0/22, 152.89.132.0/22, 152.89.196.0/24, 152.89.216.0/22, 153.51.32.0/19, 153.51.64.0/18, 153.51.160.0/19, 153.76.160.0/20, 153.76.178.0/23, 153.79.176.0/20, 153.80.172.0/22, 153.80.176.0/20, 153.80.224.0/19, 155.212.128.0/20, 155.212.144.0/21, 155.212.152.0/23, 155.212.156.0/22, 155.212.160.0/19, 155.212.192.0/20, 155.212.208.0/22, 155.212.214.0/23, 155.212.216.0/21, 155.212.224.0/21, 155.212.234.0/23, 155.212.236.0/22, 155.212.245.0/24, 155.212.246.0/23, 155.212.248.0/21, 157.22.152.0/22, 157.22.158.0/23, 157.22.160.0/19, 157.22.192.0/20, 157.22.216.0/22, 157.22.220.0/23, 157.22.224.0/21, 157.22.240.0/21, 157.22.248.0/22, 157.22.252.0/23, 157.22.254.0/24, 157.186.0.0/16, 157.228.73.0/24, 157.228.76.0/22, 157.228.96.0/23, 158.46.0.0/16, 158.58.128.0/21, 158.160.0.0/16, 158.250.0.0/16, 158.255.0.0/21, 158.255.48.0/21, 158.255.73.0/24, 158.255.75.0/24, 158.255.80.0/21, 158.255.128.0/18, 159.93.0.0/16, 159.194.192.0/24, 159.194.196.0/22, 159.194.200.0/21, 159.194.208.0/20, 159.194.224.0/19, 159.200.128.0/18, 159.200.200.0/23, 159.253.120.0/24, 159.253.168.0/21, 159.255.0.0/19, 159.255.64.0/18, 160.5.208.0/20, 160.20.156.0/22, 161.104.16.0/20, 161.104.32.0/19, 161.104.92.0/22, 161.104.96.0/20, 164.40.232.0/24, 164.138.32.0/21, 164.138.88.0/21, 164.138.100.0/22, 164.177.176.0/21, 164.215.48.0/21, 164.215.64.0/19, 168.113.128.0/18, 168.113.192.0/20, 168.113.208.0/21, 168.113.232.0/21, 168.113.240.0/20, 168.222.128.0/20, 168.222.176.0/22, 168.222.192.0/20, 168.222.224.0/21, 171.22.72.0/22, 171.22.180.0/22, 171.22.212.0/22, 171.22.236.0/22, 171.22.242.0/24, 171.25.164.0/22, 171.25.172.0/22, 171.25.177.0/24, 171.25.184.0/22, 171.25.188.0/23, 171.25.190.0/24, 171.25.194.0/23, 171.25.202.0/23, 171.25.212.0/22, 171.25.232.0/22, 171.25.242.0/23, 171.25.244.0/22, 171.25.248.0/21, 171.33.244.0/22, 171.33.248.0/21, 176.10.91.0/24, 176.10.93.0/24, 176.12.64.0/20, 176.12.96.0/21, 176.14.0.0/15, 176.28.64.0/21, 176.32.32.0/21, 176.32.128.0/19, 176.32.176.0/20, 176.32.240.0/21, 176.48.0.0/14, 176.52.0.0/17, 176.53.132.0/24, 176.53.146.0/23, 176.53.148.0/22, 176.53.152.0/22, 176.53.180.0/22, 176.53.192.0/20, 176.53.208.0/21, 176.53.224.0/19, 176.56.0.0/19, 176.56.32.0/24, 176.56.48.0/21, 176.57.64.0/22, 176.57.72.0/21, 176.57.208.0/20, 176.58.32.0/19, 176.59.0.0/16, 176.62.64.0/18, 176.62.176.0/20, 176.62.216.0/21, 176.62.240.0/20, 176.65.32.0/19, 176.65.96.0/19, 176.67.48.0/21, 176.67.240.0/21, 176.74.8.0/21, 176.74.200.0/21, 176.77.0.0/17, 176.96.0.0/19, 176.96.64.0/21, 176.96.80.0/21, 176.96.184.0/21, 176.97.32.0/20, 176.97.96.0/20, 176.97.128.0/21, 176.97.160.0/19, 176.97.193.0/24, 176.97.194.0/24, 176.98.48.0/21, 176.98.128.0/19, 176.98.168.0/21, 176.99.0.0/20, 176.99.56.0/21, 176.99.64.0/18, 176.99.128.0/17, 176.100.64.0/18, 176.100.128.0/19, 176.100.216.0/21, 176.100.240.0/20, 176.101.0.0/20, 176.101.64.0/21, 176.101.88.0/21, 176.101.96.0/19, 176.101.184.0/21, 176.101.224.0/19, 176.102.0.0/19, 176.102.68.0/22, 176.102.160.0/21, 176.103.96.0/20, 176.103.208.0/22, 176.104.120.0/21, 176.104.128.0/19, 176.104.160.0/20, 176.104.192.0/19, 176.105.234.0/24, 176.106.8.0/21, 176.106.16.0/20, 176.106.64.0/19, 176.106.128.0/21, 176.106.144.0/20, 176.106.232.0/21, 176.106.240.0/20, 176.107.0.0/20, 176.107.80.0/21, 176.107.96.0/21, 176.107.160.0/21, 176.107.200.0/21, 176.107.208.0/20, 176.107.224.0/19, 176.108.48.0/21, 176.108.144.0/20, 176.108.160.0/19, 176.108.192.0/19, 176.108.240.0/20, 176.109.0.0/19, 176.109.32.0/20, 176.109.64.0/18, 176.109.144.0/20, 176.109.176.0/20, 176.109.224.0/19, 176.110.64.0/19, 176.110.120.0/22, 176.110.127.0/24, 176.110.128.0/20, 176.110.160.0/19, 176.110.192.0/20, 176.110.208.0/21, 176.110.224.0/19, 176.111.8.0/21, 176.111.64.0/20, 176.111.96.0/21, 176.111.174.0/24, 176.111.208.0/21, 176.111.248.0/21, 176.112.32.0/19, 176.112.64.0/21, 176.112.88.0/21, 176.112.96.0/20, 176.112.128.0/20, 176.112.160.0/20, 176.112.192.0/18, 176.113.0.0/19, 176.113.32.0/21, 176.113.48.0/20, 176.113.88.0/22, 176.113.114.0/24, 176.113.117.0/24, 176.113.119.0/24, 176.113.120.0/21, 176.113.224.0/19, 176.114.16.0/20, 176.114.32.0/20, 176.114.48.0/21, 176.114.64.0/19, 176.114.112.0/20, 176.114.128.0/18, 176.114.192.0/19, 176.114.224.0/21, 176.115.32.0/19, 176.115.64.0/20, 176.115.88.0/21, 176.115.104.0/21, 176.115.112.0/20, 176.115.128.0/19, 176.115.160.0/21, 176.115.192.0/19, 176.116.0.0/24, 176.116.48.0/20, 176.116.136.0/21, 176.116.146.0/23, 176.116.160.0/19, 176.116.208.0/20, 176.116.240.0/20, 176.117.0.0/19, 176.117.32.0/20, 176.117.48.0/21, 176.117.112.0/20, 176.117.128.0/19, 176.117.192.0/18, 176.118.0.0/22, 176.118.8.0/21, 176.118.16.0/20, 176.118.32.0/24, 176.118.37.0/24, 176.118.64.0/20, 176.118.112.0/20, 176.118.128.0/21, 176.118.208.0/20, 176.118.224.0/19, 176.119.0.0/21, 176.119.16.0/21, 176.119.143.0/24, 176.119.147.0/24, 176.119.160.0/19, 176.119.197.0/24, 176.119.218.0/24, 176.119.240.0/21, 176.120.0.0/20, 176.120.21.0/24, 176.120.22.0/24, 176.120.68.0/22, 176.120.76.0/22, 176.120.80.0/20, 176.120.176.0/20, 176.120.192.0/18, 176.121.16.0/21, 176.121.48.0/22, 176.121.60.0/22, 176.121.128.0/18, 176.121.192.0/19, 176.122.16.0/20, 176.122.48.0/20, 176.122.64.0/20, 176.122.200.0/21, 176.122.247.0/24, 176.122.248.0/21, 176.123.49.0/24, 176.123.56.0/24, 176.123.128.0/19, 176.123.160.0/20, 176.123.188.0/22, 176.123.192.0/20, 176.123.216.0/22, 176.124.0.0/19, 176.124.116.0/22, 176.124.120.0/21, 176.124.136.0/23, 176.124.144.0/22, 176.124.150.0/23, 176.124.152.0/21, 176.124.160.0/21, 176.124.168.0/22, 176.124.188.0/23, 176.124.192.0/19, 176.124.226.0/23, 176.124.248.0/24, 176.125.64.0/18, 176.125.128.0/18, 176.125.192.0/19, 176.125.240.0/22, 176.126.0.0/19, 176.126.32.0/22, 176.126.36.0/24, 176.126.40.0/21, 176.126.48.0/21, 176.192.0.0/14, 176.196.0.0/15, 176.208.0.0/13, 176.221.0.0/19, 176.222.16.0/21, 176.222.192.0/20, 176.222.216.0/21, 176.222.240.0/20, 176.226.128.0/17, 176.227.176.0/20, 176.241.96.0/21, 176.241.192.0/19, 176.241.224.0/21, 178.16.80.0/20, 178.16.144.0/21, 178.16.152.0/22, 178.16.156.0/24, 178.16.158.0/23, 178.17.128.0/20, 178.17.176.0/20, 178.17.192.0/20, 178.18.0.0/20, 178.18.96.0/20, 178.18.208.0/20, 178.19.240.0/20, 178.20.40.0/21, 178.20.128.0/21, 178.20.232.0/21, 178.21.8.0/21, 178.21.24.0/21, 178.21.64.0/21, 178.22.24.0/24, 178.22.31.0/24, 178.22.40.0/21, 178.22.48.0/21, 178.22.88.0/21, 178.22.192.0/21, 178.23.0.0/21, 178.23.144.0/21, 178.23.224.0/21, 178.34.0.0/15, 178.44.0.0/14, 178.49.0.0/16, 178.57.0.0/18, 178.57.64.0/24, 178.57.69.0/24, 178.57.70.0/24, 178.57.72.0/21, 178.57.80.0/20, 178.57.96.0/19, 178.57.192.0/18, 178.64.0.0/13, 178.72.64.0/18, 178.72.128.0/18, 178.74.64.0/18, 178.74.128.0/18, 178.75.0.0/17, 178.76.192.0/18, 178.78.0.0/18, 178.129.0.0/16, 178.130.0.0/19, 178.130.32.0/21, 178.130.44.0/22, 178.130.48.0/20, 178.130.128.0/20, 178.132.96.0/21, 178.132.200.0/21, 178.140.0.0/15, 178.154.128.0/17, 178.155.0.0/17, 178.157.128.0/18, 178.158.0.0/17, 178.159.16.0/20, 178.159.32.0/23, 178.159.36.0/24, 178.159.40.0/23, 178.159.48.0/20, 178.159.80.0/20, 178.159.112.0/20, 178.159.248.0/21, 178.161.128.0/17, 178.162.0.0/17, 178.163.0.0/17, 178.166.128.0/17, 178.167.0.0/17, 178.169.32.0/19, 178.169.64.0/18, 178.170.164.0/23, 178.170.191.0/24, 178.170.192.0/22, 178.170.196.0/23, 178.170.198.0/24, 178.170.217.0/24, 178.170.218.0/23, 178.170.220.0/22, 178.170.224.0/23, 178.170.229.0/24, 178.170.242.0/24, 178.171.0.0/17, 178.173.0.0/17, 178.175.168.0/21, 178.176.0.0/14, 178.184.0.0/14, 178.204.0.0/14, 178.208.64.0/19, 178.208.128.0/19, 178.208.224.0/19, 178.209.88.0/21, 178.209.96.0/19, 178.209.192.0/18, 178.210.0.0/18, 178.210.64.0/19, 178.211.0.0/19, 178.211.153.0/24, 178.211.160.0/19, 178.212.8.0/21, 178.212.36.0/22, 178.212.64.0/21, 178.212.80.0/21, 178.212.128.0/21, 178.212.176.0/21, 178.212.208.0/21, 178.212.248.0/22, 178.213.8.0/21, 178.213.16.0/20, 178.213.32.0/21, 178.213.78.0/23, 178.213.80.0/21, 178.213.112.0/21, 178.213.144.0/21, 178.213.192.0/20, 178.213.208.0/21, 178.213.224.0/21, 178.213.240.0/20, 178.214.32.0/19, 178.214.160.0/19, 178.214.224.0/19, 178.215.64.0/18, 178.215.144.0/20, 178.215.232.0/22, 178.216.64.0/20, 178.216.96.0/21, 178.216.120.0/21, 178.216.152.0/21, 178.216.160.0/21, 178.216.172.0/23, 178.216.174.0/24, 178.216.176.0/21, 178.216.216.0/21, 178.216.232.0/21, 178.217.0.0/21, 178.217.24.0/21, 178.217.40.0/21, 178.217.48.0/20, 178.217.64.0/20, 178.217.96.0/22, 178.217.100.0/23, 178.217.103.0/24, 178.217.104.0/21, 178.217.120.0/21, 178.217.152.0/21, 178.217.160.0/21, 178.217.176.0/21, 178.217.200.0/21, 178.217.224.0/21, 178.218.0.0/18, 178.218.80.0/20, 178.218.96.0/19, 178.218.156.0/22, 178.218.208.0/20, 178.219.36.0/22, 178.219.40.0/21, 178.219.144.0/21, 178.219.152.0/23, 178.219.156.0/23, 178.219.158.0/24, 178.219.160.0/19, 178.219.192.0/19, 178.219.240.0/20, 178.234.0.0/16, 178.236.16.0/20, 178.236.128.0/20, 178.236.208.0/20, 178.236.240.0/20, 178.237.16.0/20, 178.237.60.0/22, 178.237.176.0/20, 178.237.192.0/20, 178.237.240.0/20, 178.238.16.0/20, 178.238.112.0/20, 178.248.64.0/21, 178.248.80.0/21, 178.248.232.0/21, 178.249.56.0/21, 178.249.64.0/20, 178.249.128.0/21, 178.249.240.0/21, 178.250.152.0/21, 178.250.240.0/21, 178.251.96.0/21, 178.251.136.0/21, 178.251.216.0/21, 178.252.64.0/18, 178.252.192.0/18, 178.255.120.0/21, 178.255.232.0/21, 178.255.248.0/21, 185.0.12.0/22, 185.0.44.0/22, 185.1.0.0/24, 185.1.13.0/24, 185.1.19.0/24, 185.1.23.0/24, 185.1.41.0/24, 185.1.64.0/24, 185.1.67.0/24, 185.1.72.0/24, 185.1.76.0/24, 185.1.81.0/24, 185.1.117.0/24, 185.1.118.0/24, 185.1.162.0/24, 185.1.165.0/24, 185.2.72.0/22, 185.2.88.0/22, 185.2.104.0/22, 185.2.124.0/22, 185.2.184.0/22, 185.3.16.0/22, 185.3.32.0/22, 185.3.68.0/22, 185.3.140.0/22, 185.3.172.0/22, 185.3.180.0/22, 185.3.240.0/22, 185.4.56.0/22, 185.4.108.0/22, 185.4.144.0/22, 185.4.204.0/22, 185.5.16.0/22, 185.5.136.0/21, 185.5.148.0/22, 185.5.160.0/22, 185.5.192.0/22, 185.5.204.0/23, 185.6.44.0/22, 185.6.80.0/22, 185.6.116.0/22, 185.6.124.0/22, 185.6.164.0/22, 185.6.172.0/22, 185.6.216.0/22, 185.6.228.0/22, 185.6.236.0/22, 185.6.244.0/22, 185.6.248.0/22, 185.7.28.0/22, 185.7.40.0/22, 185.7.48.0/22, 185.7.84.0/22, 185.7.92.0/22, 185.7.116.0/22, 185.7.144.0/22, 185.7.152.0/22, 185.7.180.0/22, 185.7.200.0/22, 185.7.232.0/22, 185.8.4.0/22, 185.8.20.0/22, 185.8.124.0/22, 185.8.156.0/22, 185.8.200.0/22, 185.8.216.0/21, 185.9.72.0/21, 185.9.80.0/21, 185.9.144.0/22, 185.9.160.0/22, 185.9.200.0/21, 185.9.228.0/22, 185.10.0.0/22, 185.10.44.0/22, 185.10.60.0/22, 185.10.94.0/24, 185.10.128.0/22, 185.10.152.0/22, 185.10.172.0/22, 185.10.180.0/22, 185.10.184.0/22, 185.11.4.0/22, 185.11.32.0/22, 185.11.48.0/22, 185.11.72.0/22, 185.11.132.0/22, 185.11.148.0/22, 185.11.196.0/22, 185.11.228.0/22, 185.11.244.0/22, 185.12.28.0/22, 185.12.52.0/22, 185.12.68.0/22, 185.12.84.0/22, 185.12.92.0/22, 185.12.124.0/22, 185.12.152.0/22, 185.12.208.0/22, 185.12.224.0/21, 185.12.252.0/22, 185.13.0.0/22, 185.13.24.0/22, 185.13.44.0/22, 185.13.84.0/24, 185.13.112.0/21, 185.13.120.0/22, 185.13.132.0/22, 185.13.136.0/22, 185.13.160.0/22, 185.13.176.0/22, 185.14.4.0/22, 185.14.16.0/22, 185.14.32.0/22, 185.14.40.0/22, 185.14.46.0/24, 185.14.68.0/22, 185.15.36.0/22, 185.15.60.0/22, 185.15.84.0/22, 185.15.88.0/22, 185.15.96.0/22, 185.15.116.0/22, 185.15.172.0/22, 185.15.188.0/22, 185.16.8.0/22, 185.16.28.0/22, 185.16.56.0/22, 185.16.64.0/22, 185.16.100.0/22, 185.16.104.0/22, 185.16.116.0/22, 185.16.136.0/22, 185.16.148.0/22, 185.16.176.0/22, 185.16.212.0/22, 185.16.244.0/22, 185.17.8.0/22, 185.17.16.0/22, 185.17.28.0/22, 185.17.36.0/22, 185.17.76.0/22, 185.17.84.0/22, 185.17.88.0/22, 185.17.120.0/22, 185.17.128.0/21, 185.17.140.0/22, 185.17.152.0/22, 185.17.168.0/22, 185.17.200.0/22, 185.18.4.0/22, 185.18.20.0/22, 185.18.44.0/22, 185.18.88.0/22, 185.18.108.0/22, 185.18.112.0/21, 185.18.124.0/22, 185.18.200.0/22, 185.19.20.0/22, 185.19.204.0/22, 185.20.44.0/22, 185.20.76.0/22, 185.20.80.0/22, 185.20.132.0/22, 185.20.176.0/21, 185.20.224.0/22, 185.21.49.0/24, 185.21.64.0/22, 185.21.156.0/22, 185.22.24.0/22, 185.22.60.0/22, 185.22.152.0/22, 185.22.180.0/22, 185.22.204.0/22, 185.22.232.0/22, 185.23.8.0/22, 185.23.32.0/22, 185.23.64.0/22, 185.23.80.0/22, 185.23.228.0/22, 185.24.40.0/21, 185.24.52.0/22, 185.24.56.0/22, 185.24.92.0/22, 185.24.108.0/22, 185.24.112.0/22, 185.24.176.0/21, 185.24.212.0/22, 185.24.244.0/22, 185.25.4.0/22, 185.25.8.0/22, 185.25.16.0/22, 185.25.60.0/22, 185.25.68.0/22, 185.25.88.0/22, 185.25.152.0/22, 185.25.176.0/22, 185.26.28.0/22, 185.26.40.0/22, 185.26.52.0/22, 185.26.100.0/22, 185.26.112.0/22, 185.26.120.0/22, 185.26.168.0/22, 185.26.192.0/22, 185.26.204.0/22, 185.26.212.0/23, 185.26.216.0/22, 185.27.48.0/22, 185.27.80.0/22, 185.27.100.0/22, 185.27.148.0/22, 185.27.160.0/22, 185.27.192.0/22, 185.27.208.0/22, 185.28.108.0/22, 185.28.236.0/22, 185.29.52.0/22, 185.29.56.0/22, 185.29.124.0/22, 185.29.128.0/22, 185.30.12.0/22, 185.30.16.0/22, 185.30.40.0/22, 185.30.96.0/22, 185.30.104.0/22, 185.30.116.0/22, 185.30.176.0/22, 185.30.192.0/22, 185.30.220.0/22, 185.30.228.0/22, 185.31.11.0/24, 185.31.108.0/22, 185.31.112.0/21, 185.31.132.0/22, 185.31.160.0/21, 185.31.192.0/22, 185.31.201.0/24, 185.32.56.0/22, 185.32.84.0/22, 185.32.120.0/22, 185.32.132.0/22, 185.32.164.0/22, 185.32.184.0/22, 185.32.248.0/22, 185.33.120.0/22, 185.33.140.0/22, 185.33.160.0/22, 185.33.172.0/22, 185.33.196.0/22, 185.33.200.0/22, 185.33.228.0/22, 185.33.236.0/22, 185.33.244.0/22, 185.34.20.0/22, 185.34.152.0/22, 185.34.180.0/22, 185.34.240.0/21, 185.35.0.0/21, 185.35.36.0/22, 185.35.40.0/21, 185.35.116.0/22, 185.35.120.0/22, 185.35.128.0/22, 185.35.160.0/22, 185.35.168.0/22, 185.35.192.0/22, 185.36.23.0/24, 185.36.60.0/22, 185.36.104.0/22, 185.36.156.0/22, 185.36.172.0/22, 185.37.48.0/22, 185.37.56.0/21, 185.37.128.0/22, 185.37.164.0/22, 185.37.188.0/22, 185.38.16.0/22, 185.38.84.0/22, 185.38.160.0/22, 185.38.204.0/22, 185.39.4.0/22, 185.39.80.0/22, 185.39.112.0/21, 185.39.132.0/22, 185.39.192.0/22, 185.39.204.0/22, 185.40.0.0/22, 185.40.28.0/22, 185.40.40.0/22, 185.40.76.0/22, 185.40.105.0/24, 185.40.128.0/22, 185.40.152.0/22, 185.41.40.0/22, 185.41.100.0/22, 185.41.112.0/22, 185.41.120.0/22, 185.41.160.0/22, 185.41.168.0/22, 185.41.184.0/22, 185.41.196.0/22, 185.41.204.0/22, 185.42.24.0/22, 185.42.40.0/22, 185.42.48.0/22, 185.42.60.0/22, 185.42.76.0/22, 185.42.80.0/22, 185.42.88.0/21, 185.42.108.0/22, 185.42.124.0/22, 185.42.144.0/22, 185.42.156.0/22, 185.42.163.0/24, 185.42.164.0/22, 185.42.180.0/22, 185.42.228.0/22, 185.43.4.0/22, 185.43.84.0/22, 185.43.92.0/22, 185.43.100.0/22, 185.43.144.0/23, 185.43.196.0/22, 185.44.0.0/22, 185.44.8.0/21, 185.44.68.0/22, 185.44.120.0/22, 185.44.164.0/22, 185.44.236.0/22, 185.45.60.0/22, 185.45.80.0/22, 185.45.100.0/22, 185.45.144.0/22, 185.46.8.0/21, 185.46.16.0/22, 185.46.44.0/22, 185.46.48.0/22, 185.46.96.0/22, 185.46.152.0/22, 185.46.196.0/22, 185.47.36.0/22, 185.47.52.0/22, 185.47.154.0/23, 185.47.165.0/24, 185.47.204.0/22, 185.48.16.0/22, 185.48.36.0/22, 185.48.56.0/22, 185.48.72.0/22, 185.48.112.0/22, 185.48.236.0/22, 185.49.64.0/22, 185.49.108.0/22, 185.49.240.0/22, 185.50.7.0/24, 185.50.8.0/22, 185.50.24.0/22, 185.50.80.0/22, 185.50.136.0/22, 185.50.156.0/22, 185.50.200.0/22, 185.50.216.0/22, 185.51.21.0/24, 185.51.124.0/22, 185.51.140.0/22, 185.51.156.0/22, 185.51.208.0/22, 185.52.28.0/22, 185.52.56.0/22, 185.52.68.0/22, 185.52.72.0/21, 185.52.132.0/22, 185.52.140.0/22, 185.52.152.0/22, 185.53.20.0/22, 185.53.104.0/22, 185.53.232.0/22, 185.53.251.0/24, 185.54.20.0/22, 185.54.40.0/22, 185.54.48.0/22, 185.54.136.0/22, 185.54.176.0/22, 185.54.236.0/22, 185.54.244.0/22, 185.55.0.0/22, 185.55.40.0/22, 185.55.56.0/22, 185.55.172.0/22, 185.56.116.0/22, 185.57.28.0/22, 185.57.236.0/22, 185.57.248.0/22, 185.58.112.0/22, 185.58.124.0/22, 185.58.152.0/22, 185.58.188.0/22, 185.58.216.0/21, 185.58.248.0/21, 185.59.56.0/22, 185.59.136.0/21, 185.59.172.0/22, 185.59.192.0/22, 185.59.216.0/23, 185.59.218.0/24, 185.59.232.0/22, 185.59.244.0/22, 185.60.12.0/22, 185.60.44.0/22, 185.60.48.0/22, 185.60.132.0/22, 185.60.152.0/22, 185.60.212.0/22, 185.61.24.0/22, 185.61.36.0/22, 185.61.76.0/22, 185.61.92.0/22, 185.61.128.0/22, 185.61.172.0/22, 185.61.192.0/22, 185.61.216.0/21, 185.61.244.0/22, 185.61.252.0/22, 185.62.100.0/22, 185.62.192.0/22, 185.62.200.0/22, 185.62.229.0/24, 185.63.60.0/22, 185.63.188.0/22, 185.63.196.0/22, 185.63.216.0/22, 185.64.7.0/24, 185.64.44.0/22, 185.64.52.0/22, 185.64.76.0/24, 185.64.208.0/22, 185.64.228.0/22, 185.65.12.0/22, 185.65.16.0/21, 185.65.138.0/23, 185.65.148.0/22, 185.65.200.0/22, 185.66.12.0/22, 185.66.28.0/22, 185.66.84.0/22, 185.66.216.0/22, 185.67.52.0/22, 185.67.92.0/22, 185.67.160.0/21, 185.67.168.0/22, 185.67.244.0/22, 185.68.80.0/22, 185.68.92.0/22, 185.68.100.0/22, 185.68.116.0/22, 185.68.144.0/22, 185.68.152.0/22, 185.68.184.0/22, 185.68.244.0/22, 185.69.80.0/22, 185.69.124.0/22, 185.69.132.0/22, 185.69.220.0/22, 185.70.104.0/22, 185.70.128.0/22, 185.70.164.0/22, 185.71.52.0/22, 185.71.64.0/21, 185.71.76.0/22, 185.71.80.0/22, 185.71.96.0/22, 185.71.144.0/22, 185.71.196.0/22, 185.71.212.0/22, 185.72.144.0/22, 185.72.188.0/22, 185.72.224.0/21, 185.73.16.0/22, 185.73.180.0/22, 185.73.192.0/22, 185.73.212.0/22, 185.73.216.0/22, 185.74.104.0/22, 185.74.228.0/22, 185.75.4.0/22, 185.75.44.0/22, 185.75.64.0/22, 185.75.84.0/22, 185.75.88.0/22, 185.75.132.0/22, 185.75.180.0/22, 185.75.188.0/22, 185.75.220.0/23, 185.75.248.0/22, 185.76.80.0/22, 185.76.108.0/22, 185.76.144.0/22, 185.76.220.0/22, 185.76.232.0/22, 185.76.240.0/22, 185.76.252.0/22, 185.77.8.0/22, 185.77.16.0/22, 185.77.136.0/22, 185.77.148.0/22, 185.77.204.0/22, 185.77.216.0/21, 185.77.228.0/22, 185.77.232.0/21, 185.77.240.0/22, 185.78.28.0/22, 185.78.32.0/22, 185.78.76.0/22, 185.78.92.0/22, 185.78.108.0/22, 185.78.112.0/21, 185.78.236.0/22, 185.79.48.0/22, 185.79.68.0/22, 185.79.76.0/22, 185.79.100.0/22, 185.79.116.0/22, 185.79.132.0/22, 185.79.136.0/22, 185.79.236.0/22, 185.80.28.0/24, 185.80.148.0/22, 185.80.204.0/22, 185.80.228.0/22, 185.80.248.0/22, 185.81.24.0/22, 185.81.64.0/22, 185.81.144.0/22, 185.81.172.0/22, 185.81.184.0/22, 185.81.220.0/22, 185.81.248.0/22, 185.82.12.0/22, 185.82.24.0/22, 185.82.176.0/22, 185.82.196.0/22, 185.82.244.0/22, 185.83.240.0/22, 185.84.32.0/22, 185.84.40.0/22, 185.84.108.0/22, 185.84.172.0/22, 185.84.184.0/21, 185.84.244.0/22, 185.85.160.0/22, 185.85.216.0/22, 185.86.112.0/20, 185.86.132.0/22, 185.86.144.0/22, 185.87.192.0/21, 185.88.36.0/22, 185.88.96.0/21, 185.88.124.0/22, 185.88.156.0/22, 185.88.160.0/22, 185.88.208.0/22, 185.89.8.0/21, 185.89.40.0/22, 185.89.99.0/24, 185.89.104.0/21, 185.90.100.0/22, 185.90.144.0/22, 185.90.224.0/22, 185.91.52.0/22, 185.91.125.0/24, 185.91.176.0/21, 185.91.252.0/22, 185.92.32.0/22, 185.92.136.0/21, 185.92.144.0/22, 185.92.184.0/22, 185.93.40.0/22, 185.93.108.0/22, 185.93.208.0/22, 185.93.252.0/22, 185.94.31.0/24, 185.94.32.0/22, 185.94.108.0/22, 185.94.152.0/22, 185.94.172.0/22, 185.94.182.0/24, 185.94.212.0/22, 185.94.224.0/22, 185.95.66.0/24, 185.95.100.0/22, 185.95.104.0/22, 185.95.228.0/22, 185.96.0.0/22, 185.96.36.0/22, 185.96.80.0/21, 185.96.132.0/22, 185.97.0.0/22, 185.97.76.0/22, 185.97.164.0/22, 185.97.200.0/22, 185.97.252.0/22, 185.98.40.0/22, 185.98.80.0/21, 185.99.4.0/22, 185.99.8.0/21, 185.99.140.0/23, 185.99.152.0/21, 185.99.160.0/22, 185.99.168.0/22, 185.100.24.0/22, 185.100.100.0/22, 185.100.104.0/22, 185.100.156.0/22, 185.101.24.0/22, 185.101.137.0/24, 185.101.200.0/21, 185.102.8.0/22, 185.102.112.0/22, 185.102.120.0/22, 185.102.180.0/22, 185.103.24.0/22, 185.103.60.0/22, 185.103.68.0/22, 185.103.100.0/22, 185.103.132.0/22, 185.103.160.0/21, 185.103.200.0/24, 185.104.4.0/22, 185.104.92.0/22, 185.104.104.0/22, 185.104.112.0/22, 185.104.148.0/22, 185.104.212.0/22, 185.105.44.0/22, 185.105.48.0/22, 185.105.108.0/22, 185.105.168.0/22, 185.105.224.0/22, 185.106.40.0/22, 185.106.56.0/22, 185.106.104.0/23, 185.106.107.0/24, 185.106.116.0/24, 185.106.172.0/22, 185.106.196.0/22, 185.107.72.0/22, 185.107.124.0/22, 185.107.128.0/22, 185.107.136.0/22, 185.107.156.0/22, 185.107.164.0/22, 185.107.240.0/22, 185.107.252.0/22, 185.108.0.0/21, 185.108.16.0/21, 185.108.88.0/22, 185.108.160.0/22, 185.108.192.0/21, 185.108.208.0/21, 185.108.220.0/22, 185.109.112.0/22, 185.109.160.0/24, 185.109.204.0/22, 185.109.208.0/22, 185.110.44.0/22, 185.110.56.0/22, 185.110.128.0/22, 185.110.168.0/22, 185.110.208.0/22, 185.111.24.0/22, 185.111.48.0/22, 185.111.84.0/22, 185.111.100.0/22, 185.111.164.0/22, 185.111.216.0/22, 185.112.56.0/22, 185.112.75.0/24, 185.112.120.0/22, 185.112.140.0/22, 185.112.224.0/22, 185.112.236.0/22, 185.112.250.0/24, 185.113.72.0/21, 185.113.108.0/22, 185.113.116.0/22, 185.113.136.0/22, 185.113.200.0/22, 185.113.208.0/22, 185.113.244.0/22, 185.114.244.0/22, 185.115.92.0/22, 185.115.96.0/22, 185.115.140.0/22, 185.115.156.0/22, 185.116.119.0/24, 185.116.196.0/22, 185.116.200.0/22, 185.116.228.0/22, 185.117.28.0/22, 185.117.100.0/22, 185.117.140.0/22, 185.117.144.0/22, 185.117.156.0/22, 185.117.232.0/22, 185.117.244.0/22, 185.118.64.0/22, 185.118.126.0/24, 185.118.164.0/22, 185.119.0.0/22, 185.119.56.0/22, 185.119.64.0/22, 185.120.56.0/22, 185.120.88.0/22, 185.120.184.0/21, 185.121.72.0/22, 185.121.84.0/22, 185.121.244.0/22, 185.122.28.0/22, 185.122.204.0/24, 185.123.12.0/22, 185.123.40.0/21, 185.123.64.0/22, 185.123.76.0/22, 185.123.164.0/22, 185.123.172.0/22, 185.123.192.0/22, 185.124.12.0/22, 185.124.64.0/24, 185.124.152.0/22, 185.124.164.0/22, 185.124.176.0/22, 185.124.188.0/22, 185.124.208.0/22, 185.124.228.0/22, 185.125.16.0/22, 185.125.44.0/22, 185.125.56.0/22, 185.125.72.0/22, 185.125.100.0/22, 185.125.112.0/22, 185.125.140.0/24, 185.125.200.0/22, 185.126.84.0/22, 185.126.92.0/22, 185.126.128.0/22, 185.126.180.0/22, 185.126.192.0/22, 185.127.148.0/22, 185.127.224.0/22, 185.127.244.0/22, 185.128.4.0/22, 185.128.84.0/22, 185.128.196.0/22, 185.129.56.0/22, 185.129.96.0/21, 185.129.128.0/22, 185.129.144.0/22, 185.129.204.0/22, 185.130.80.0/22, 185.130.112.0/22, 185.130.212.0/22, 185.130.232.0/22, 185.130.248.0/22, 185.131.68.0/22, 185.131.172.0/22, 185.132.20.0/22, 185.132.28.0/22, 185.132.196.0/22, 185.132.240.0/22, 185.133.0.0/22, 185.133.40.0/22, 185.134.32.0/21, 185.134.72.0/22, 185.134.120.0/22, 185.134.148.0/22, 185.134.204.0/22, 185.134.232.0/22, 185.135.44.0/24, 185.135.80.0/22, 185.135.148.0/22, 185.135.172.0/22, 185.135.180.0/22, 185.135.232.0/22, 185.136.76.0/22, 185.136.184.0/22, 185.137.76.0/22, 185.137.160.0/22, 185.137.176.0/22, 185.137.232.0/22, 185.138.8.0/22, 185.138.76.0/22, 185.138.88.0/22, 185.138.128.0/22, 185.138.160.0/22, 185.138.180.0/22, 185.138.200.0/21, 185.138.252.0/22, 185.139.108.0/22, 185.139.212.0/22, 185.140.132.0/22, 185.140.148.0/22, 185.140.175.0/24, 185.141.40.0/22, 185.141.76.0/22, 185.141.92.0/22, 185.141.124.0/22, 185.141.180.0/22, 185.141.224.0/22, 185.141.232.0/21, 185.142.64.0/22, 185.142.96.0/22, 185.142.128.0/23, 185.143.108.0/22, 185.143.172.0/22, 185.143.236.0/24, 185.144.28.0/22, 185.144.60.0/22, 185.144.114.0/24, 185.144.136.0/22, 185.144.200.0/22, 185.145.56.0/22, 185.145.124.0/22, 185.145.140.0/22, 185.146.72.0/22, 185.146.116.0/22, 185.146.156.0/22, 185.146.168.0/22, 185.146.212.0/22, 185.147.39.0/24, 185.147.80.0/22, 185.148.36.0/22, 185.148.80.0/22, 185.148.104.0/22, 185.148.208.0/22, 185.148.216.0/21, 185.149.48.0/22, 185.149.64.0/22, 185.149.96.0/22, 185.149.124.0/22, 185.149.144.0/22, 185.149.160.0/22, 185.149.194.0/24, 185.149.196.0/22, 185.149.240.0/21, 185.150.12.0/22, 185.150.152.0/22, 185.151.120.0/22, 185.151.128.0/22, 185.151.240.0/22, 185.152.80.0/22, 185.152.92.0/22, 185.152.132.0/22, 185.152.184.0/22, 185.153.44.0/22, 185.153.92.0/22, 185.153.132.0/22, 185.153.240.0/22, 185.154.20.0/22, 185.154.52.0/22, 185.154.72.0/22, 185.154.120.0/21, 185.154.192.0/22, 185.154.200.0/22, 185.155.16.0/22, 185.155.112.0/22, 185.155.118.0/24, 185.156.0.0/22, 185.156.72.0/22, 185.156.176.0/21, 185.157.96.0/22, 185.157.120.0/24, 185.157.124.0/22, 185.157.188.0/22, 185.157.252.0/22, 185.158.112.0/22, 185.158.152.0/22, 185.159.44.0/22, 185.159.64.0/22, 185.159.111.0/24, 185.159.128.0/22, 185.159.148.0/22, 185.159.164.0/22, 185.159.172.0/22, 185.160.36.0/22, 185.160.136.0/22, 185.160.188.0/22, 185.160.216.0/22, 185.161.32.0/22, 185.161.52.0/22, 185.161.60.0/22, 185.161.64.0/22, 185.161.180.0/22, 185.161.196.0/22, 185.161.251.0/24, 185.162.12.0/22, 185.162.32.0/22, 185.162.92.0/22, 185.162.188.0/22, 185.162.204.0/22, 185.162.232.0/23, 185.162.235.0/24, 185.163.156.0/22, 185.163.176.0/22, 185.163.236.0/22, 185.163.240.0/22, 185.164.148.0/23, 185.164.150.0/24, 185.164.162.0/24, 185.164.172.0/24, 185.165.112.0/22, 185.165.120.0/22, 185.165.160.0/22, 185.165.200.0/22, 185.165.216.0/22, 185.165.224.0/22, 185.166.2.0/24, 185.166.16.0/22, 185.166.72.0/22, 185.166.196.0/22, 185.166.240.0/22, 185.167.120.0/22, 185.167.224.0/22, 185.168.60.0/22, 185.168.184.0/22, 185.168.208.0/22, 185.168.236.0/22, 185.169.100.0/22, 185.169.152.0/22, 185.169.216.0/22, 185.170.0.0/22, 185.170.10.0/24, 185.170.52.0/22, 185.170.204.0/22, 185.171.12.0/22, 185.171.80.0/22, 185.171.100.0/22, 185.171.192.0/22, 185.171.203.0/24, 185.172.136.0/22, 185.173.0.0/22, 185.173.36.0/22, 185.173.72.0/22, 185.173.80.0/22, 185.173.92.0/22, 185.173.112.0/22, 185.173.176.0/22, 185.174.128.0/22, 185.174.164.0/22, 185.174.192.0/22, 185.175.16.0/22, 185.175.44.0/22, 185.175.72.0/22, 185.175.116.0/22, 185.175.128.0/22, 185.175.156.0/22, 185.175.172.0/22, 185.175.224.0/22, 185.176.24.0/22, 185.177.96.0/22, 185.177.108.0/22, 185.177.112.0/22, 185.177.216.0/21, 185.177.236.0/22, 185.178.44.0/22, 185.178.128.0/22, 185.178.204.0/22, 185.178.208.0/22, 185.178.244.0/22, 185.179.0.0/21, 185.179.36.0/22, 185.179.72.0/22, 185.179.84.0/22, 185.179.144.0/22, 185.179.160.0/22, 185.179.172.0/22, 185.179.188.0/22, 185.179.196.0/24, 185.179.224.0/22, 185.180.40.0/22, 185.180.56.0/22, 185.180.108.0/22, 185.180.124.0/22, 185.180.160.0/22, 185.180.176.0/22, 185.180.188.0/22, 185.180.200.0/22, 185.180.212.0/22, 185.180.236.0/22, 185.180.248.0/22, 185.181.200.0/22, 185.181.244.0/22, 185.182.16.0/22, 185.182.104.0/24, 185.182.108.0/22, 185.182.128.0/22, 185.183.12.0/22, 185.183.48.0/22, 185.183.76.0/22, 185.183.160.0/22, 185.183.172.0/22, 185.183.196.0/22, 185.184.54.0/23, 185.184.76.0/24, 185.184.79.0/24, 185.184.128.0/22, 185.184.232.0/22, 185.185.0.0/22, 185.185.20.0/22, 185.185.56.0/22, 185.185.68.0/22, 185.185.104.0/22, 185.185.120.0/22, 185.185.136.0/21, 185.185.144.0/21, 185.185.180.0/22, 185.186.2.0/23, 185.186.140.0/22, 185.186.184.0/22, 185.186.192.0/22, 185.186.232.0/22, 185.187.63.0/24, 185.187.89.0/24, 185.187.90.0/23, 185.187.112.0/22, 185.187.188.0/22, 185.187.199.0/24, 185.188.96.0/22, 185.189.16.0/22, 185.189.100.0/22, 185.189.132.0/22, 185.189.164.0/24, 185.189.167.0/24, 185.189.195.0/24, 185.190.0.0/21, 185.190.40.0/22, 185.190.104.0/22, 185.190.116.0/22, 185.190.120.0/22, 185.190.164.0/22, 185.190.180.0/22, 185.190.184.0/21, 185.191.32.0/22, 185.191.56.0/22, 185.191.196.0/22, 185.192.108.0/24, 185.192.111.0/24, 185.193.20.0/22, 185.193.46.0/24, 185.193.88.0/22, 185.193.136.0/24, 185.193.139.0/24, 185.193.141.0/24, 185.193.196.0/22, 185.193.204.0/22, 185.193.248.0/22, 185.194.32.0/22, 185.194.107.0/24, 185.195.144.0/22, 185.195.152.0/22, 185.195.169.0/24, 185.195.170.0/23, 185.195.188.0/22, 185.195.243.0/24, 185.196.31.0/24, 185.196.116.0/24, 185.196.119.0/24, 185.196.168.0/22, 185.196.184.0/22, 185.196.236.0/22, 185.196.244.0/22, 185.197.12.0/22, 185.197.32.0/22, 185.197.72.0/22, 185.197.232.0/22, 185.198.40.0/22, 185.198.69.0/24, 185.198.152.0/24, 185.199.4.0/22, 185.199.8.0/22, 185.199.144.0/22, 185.199.160.0/22, 185.200.16.0/22, 185.200.120.0/24, 185.200.140.0/22, 185.200.152.0/22, 185.200.160.0/22, 185.200.168.0/23, 185.200.171.0/24, 185.200.216.0/22, 185.200.240.0/22, 185.201.44.0/23, 185.201.47.0/24, 185.201.52.0/22, 185.201.88.0/22, 185.202.0.0/22, 185.202.12.0/22, 185.202.109.0/24, 185.202.212.0/22, 185.203.72.0/22, 185.203.96.0/22, 185.203.144.0/22, 185.204.20.0/22, 185.204.110.0/24, 185.204.124.0/22, 185.204.156.0/22, 185.205.64.0/22, 185.205.76.0/22, 185.205.128.0/22, 185.205.236.0/22, 185.206.100.0/22, 185.206.164.0/22, 185.206.204.0/22, 185.207.0.0/23, 185.207.2.0/24, 185.207.22.0/24, 185.207.24.0/22, 185.207.64.0/22, 185.207.88.0/22, 185.207.252.0/22, 185.208.72.0/22, 185.208.157.0/24, 185.208.192.0/22, 185.209.24.0/21, 185.209.44.0/22, 185.209.112.0/22, 185.209.240.0/24, 185.210.44.0/22, 185.210.56.0/22, 185.210.140.0/22, 185.210.240.0/22, 185.211.0.0/22, 185.211.112.0/22, 185.211.156.0/22, 185.211.164.0/22, 185.211.224.0/22, 185.211.240.0/21, 185.212.0.0/22, 185.212.28.0/22, 185.212.88.0/22, 185.212.112.0/24, 185.212.116.0/23, 185.213.28.0/22, 185.213.136.0/22, 185.213.156.0/22, 185.213.212.0/22, 185.214.56.0/21, 185.214.76.0/22, 185.214.160.0/22, 185.214.184.0/22, 185.214.244.0/22, 185.214.252.0/22, 185.215.4.0/24, 185.215.60.0/22, 185.215.176.0/22, 185.216.16.0/22, 185.216.60.0/22, 185.216.80.0/22, 185.216.100.0/22, 185.216.192.0/22, 185.216.204.0/22, 185.216.244.0/22, 185.217.7.0/24, 185.217.128.0/24, 185.217.167.0/24, 185.218.8.0/22, 185.218.68.0/22, 185.218.108.0/22, 185.218.180.0/22, 185.218.205.0/24, 185.219.4.0/24, 185.219.40.0/22, 185.220.32.0/21, 185.220.40.0/22, 185.221.44.0/22, 185.221.116.0/22, 185.221.120.0/22, 185.221.152.0/22, 185.221.164.0/22, 185.221.196.0/22, 185.221.212.0/22, 185.222.52.0/22, 185.222.152.0/22, 185.222.236.0/22, 185.223.116.0/22, 185.223.212.0/24, 185.224.8.0/22, 185.224.31.0/24, 185.224.96.0/22, 185.224.212.0/24, 185.224.228.0/22, 185.225.32.0/22, 185.225.152.0/22, 185.225.176.0/22, 185.225.200.0/23, 185.226.52.0/22, 185.226.128.0/22, 185.226.244.0/22, 185.227.36.0/22, 185.227.84.0/22, 185.228.48.0/22, 185.228.112.0/22, 185.228.132.0/22, 185.228.232.0/22, 185.228.248.0/24, 185.229.8.0/22, 185.229.122.0/23, 185.229.184.0/22, 185.229.192.0/22, 185.229.205.0/24, 185.229.232.0/22, 185.230.140.0/22, 185.230.188.0/22, 185.231.48.0/22, 185.231.56.0/24, 185.231.152.0/22, 185.231.204.0/22, 185.231.232.0/24, 185.231.244.0/22, 185.232.16.0/22, 185.232.28.0/22, 185.232.47.0/24, 185.232.60.0/22, 185.232.104.0/22, 185.232.136.0/22, 185.232.208.0/22, 185.232.228.0/22, 185.232.233.0/24, 185.232.235.0/24, 185.233.0.0/22, 185.233.92.0/22, 185.233.185.0/24, 185.233.186.0/23, 185.233.192.0/22, 185.233.236.0/22, 185.234.9.0/24, 185.234.11.0/24, 185.234.116.0/22, 185.234.120.0/22, 185.234.216.0/24, 185.234.228.0/22, 185.235.9.0/24, 185.235.11.0/24, 185.235.28.0/22, 185.235.64.0/22, 185.235.72.0/22, 185.235.92.0/22, 185.235.230.0/23, 185.236.20.0/22, 185.236.28.0/22, 185.236.54.0/24, 185.236.64.0/21, 185.236.131.0/24, 185.236.156.0/22, 185.236.208.0/22, 185.236.216.0/22, 185.237.186.0/24, 185.237.236.0/22, 185.237.248.0/22, 185.238.76.0/22, 185.238.88.0/22, 185.238.104.0/22, 185.238.138.0/24, 185.238.152.0/22, 185.238.196.0/22, 185.238.200.0/22, 185.238.208.0/22, 185.239.48.0/22, 185.239.76.0/22, 185.239.88.0/22, 185.239.116.0/22, 185.239.120.0/22, 185.239.128.0/21, 185.240.48.0/22, 185.240.102.0/23, 185.241.57.0/24, 185.241.68.0/23, 185.241.111.0/24, 185.241.176.0/22, 185.241.192.0/22, 185.242.16.0/22, 185.242.116.0/22, 185.242.120.0/22, 185.243.84.0/22, 185.243.220.0/22, 185.244.4.0/22, 185.244.20.0/22, 185.244.44.0/22, 185.244.51.0/24, 185.244.76.0/22, 185.244.163.0/24, 185.244.180.0/22, 185.245.23.0/24, 185.245.36.0/22, 185.245.148.0/22, 185.245.184.0/22, 185.246.32.0/22, 185.246.64.0/22, 185.246.88.0/22, 185.246.116.0/22, 185.246.124.0/22, 185.246.180.0/22, 185.246.192.0/22, 185.246.216.0/22, 185.247.16.0/22, 185.247.80.0/22, 185.247.140.0/23, 185.247.142.0/24, 185.247.184.0/22, 185.247.192.0/22, 185.248.16.0/22, 185.248.100.0/22, 185.249.11.0/24, 185.250.0.0/22, 185.250.44.0/24, 185.250.46.0/23, 185.250.48.0/22, 185.250.60.0/22, 185.250.128.0/22, 185.250.228.0/22, 185.251.22.0/24, 185.251.88.0/22, 185.251.148.0/22, 185.251.216.0/21, 185.251.240.0/22, 185.252.60.0/22, 185.252.228.0/22, 185.252.248.0/22, 185.253.2.0/23, 185.253.20.0/22, 185.253.24.0/22, 185.253.32.0/22, 185.253.40.0/22, 185.253.55.0/24, 185.253.100.0/22, 185.253.104.0/22, 185.253.128.0/24, 185.253.136.0/22, 185.253.180.0/22, 185.253.184.0/22, 185.254.188.0/22, 185.254.212.0/22, 185.255.176.0/22, 188.0.0.0/19, 188.0.40.0/21, 188.0.160.0/19, 188.0.192.0/19, 188.16.0.0/14, 188.32.0.0/16, 188.35.0.0/16, 188.43.0.0/16, 188.44.32.0/19, 188.44.96.0/19, 188.64.112.0/21, 188.64.128.0/21, 188.64.136.0/23, 188.64.144.0/21, 188.64.160.0/23, 188.64.163.0/24, 188.64.164.0/22, 188.64.168.0/21, 188.64.216.0/21, 188.65.8.0/21, 188.65.48.0/21, 188.65.104.0/21, 188.65.128.0/21, 188.65.208.0/21, 188.65.232.0/21, 188.65.240.0/21, 188.66.32.0/21, 188.68.8.0/21, 188.68.16.0/20, 188.68.80.0/20, 188.68.96.0/19, 188.68.128.0/19, 188.68.160.0/22, 188.68.168.0/21, 188.68.176.0/20, 188.68.192.0/19, 188.72.76.0/23, 188.72.96.0/24, 188.72.103.0/24, 188.72.104.0/21, 188.72.112.0/23, 188.72.117.0/24, 188.72.118.0/24, 188.73.128.0/18, 188.75.0.0/18, 188.75.192.0/18, 188.92.24.0/24, 188.92.104.0/21, 188.92.176.0/21, 188.92.192.0/21, 188.92.216.0/21, 188.92.240.0/21, 188.93.16.0/20, 188.93.48.0/20, 188.93.64.0/22, 188.93.104.0/21, 188.93.128.0/21, 188.93.208.0/21, 188.93.240.0/21, 188.94.32.0/21, 188.94.48.0/21, 188.94.88.0/21, 188.94.160.0/20, 188.94.208.0/21, 188.94.224.0/21, 188.95.71.0/24, 188.95.104.0/21, 188.95.184.0/21, 188.112.192.0/18, 188.113.0.0/18, 188.113.128.0/18, 188.114.0.0/18, 188.114.192.0/19, 188.116.128.0/18, 188.119.76.0/22, 188.119.120.0/21, 188.120.48.0/20, 188.120.224.0/19, 188.122.160.0/19, 188.122.224.0/19, 188.123.32.0/19, 188.123.224.0/19, 188.124.32.0/20, 188.124.48.0/21, 188.124.96.0/19, 188.124.224.0/21, 188.124.240.0/22, 188.124.248.0/21, 188.126.32.0/19, 188.127.224.0/20, 188.127.240.0/22, 188.127.244.0/23, 188.127.248.0/22, 188.127.253.0/24, 188.127.254.0/23, 188.128.0.0/17, 188.133.128.0/17, 188.134.0.0/17, 188.143.128.0/17, 188.162.0.0/16, 188.164.136.0/21, 188.164.160.0/19, 188.164.208.0/21, 188.168.0.0/16, 188.170.0.0/16, 188.186.0.0/15, 188.190.116.0/22, 188.190.128.0/19, 188.190.192.0/19, 188.191.0.0/19, 188.191.80.0/20, 188.191.112.0/20, 188.191.160.0/20, 188.191.184.0/21, 188.191.192.0/21, 188.191.240.0/21, 188.225.0.0/17, 188.226.0.0/17, 188.227.0.0/19, 188.227.32.0/20, 188.227.48.0/21, 188.227.60.0/22, 188.227.64.0/20, 188.227.80.0/22, 188.227.88.0/21, 188.227.96.0/21, 188.227.104.0/23, 188.227.108.0/22, 188.227.112.0/20, 188.232.0.0/14, 188.240.17.0/24, 188.242.0.0/15, 188.244.0.0/20, 188.244.32.0/20, 188.244.128.0/17, 188.246.160.0/19, 188.246.224.0/21, 188.246.232.0/22, 188.247.32.0/19, 188.254.0.0/17, 188.255.0.0/17, 192.54.76.0/23, 192.54.186.0/23, 192.70.196.0/22, 192.86.11.0/24, 192.94.221.0/24, 192.94.233.0/24, 192.101.4.0/24, 192.102.229.0/24, 192.109.98.0/24, 192.109.214.0/23, 192.109.224.0/23, 192.109.243.0/24, 192.109.248.0/24, 192.109.250.0/24, 192.109.253.0/24, 192.124.174.0/24, 192.124.208.0/24, 192.124.210.0/23, 192.124.212.0/22, 192.124.217.0/24, 192.124.218.0/23, 192.136.61.0/24, 192.144.12.0/22, 192.144.28.0/24, 192.144.40.0/22, 192.144.56.0/22, 192.145.0.0/22, 192.145.12.0/22, 192.145.16.0/22, 192.145.96.0/22, 192.146.140.0/23, 192.148.166.0/24, 192.150.205.0/24, 192.160.10.0/24, 192.160.224.0/24, 192.160.233.0/24, 192.162.0.0/22, 192.162.8.0/22, 192.162.20.0/22, 192.162.36.0/22, 192.162.40.0/22, 192.162.56.0/22, 192.162.64.0/22, 192.162.88.0/22, 192.162.120.0/21, 192.162.180.0/22, 192.162.188.0/22, 192.162.192.0/22, 192.162.212.0/22, 192.162.240.0/21, 192.162.249.0/24, 192.162.250.0/23, 192.162.252.0/22, 192.166.12.0/22, 192.166.100.0/22, 192.166.116.0/22, 192.166.123.0/24, 192.166.124.0/22, 192.166.144.0/23, 192.166.232.0/21, 192.175.1.0/24, 192.188.116.0/24, 192.188.187.0/24, 192.188.189.0/24, 192.195.8.0/24, 192.203.80.0/24, 193.0.130.0/23, 193.0.132.0/22, 193.0.145.0/24, 193.0.148.0/22, 193.0.152.0/22, 193.0.166.0/23, 193.0.168.0/22, 193.0.178.0/23, 193.0.185.0/24, 193.0.214.0/23, 193.3.17.0/24, 193.3.40.0/24, 193.3.42.0/24, 193.3.136.0/24, 193.3.179.0/24, 193.3.184.0/23, 193.3.188.0/24, 193.3.230.0/24, 193.7.160.0/21, 193.7.216.0/22, 193.8.0.0/24, 193.8.201.0/24, 193.8.208.0/22, 193.9.17.0/24, 193.9.22.0/24, 193.9.60.0/22, 193.9.156.0/23, 193.9.240.0/21, 193.9.251.0/24, 193.16.40.0/23, 193.16.208.0/23, 193.16.210.0/24, 193.16.232.0/24, 193.17.52.0/24, 193.17.92.0/23, 193.17.170.0/24, 193.17.213.0/24, 193.19.66.0/23, 193.19.68.0/22, 193.19.82.0/23, 193.19.100.0/22, 193.19.110.0/23, 193.19.116.0/22, 193.19.126.0/23, 193.19.132.0/22, 193.19.168.0/22, 193.22.6.0/24, 193.22.88.0/24, 193.22.148.0/22, 193.22.242.0/24, 193.22.244.0/24, 193.23.3.0/24, 193.23.50.0/24, 193.23.55.0/24, 193.23.122.0/24, 193.23.142.0/24, 193.23.144.0/22, 193.24.8.0/22, 193.24.122.0/23, 193.24.196.0/22, 193.24.228.0/22, 193.25.8.0/21, 193.25.120.0/23, 193.25.188.0/22, 193.25.210.0/23, 193.25.221.0/24, 193.26.16.0/24, 193.26.18.0/23, 193.26.122.0/24, 193.26.127.0/24, 193.26.133.0/24, 193.26.135.0/24, 193.26.208.0/24, 193.26.214.0/24, 193.26.217.0/24, 193.27.36.0/22, 193.27.41.0/24, 193.27.90.0/24, 193.27.214.0/23, 193.27.222.0/23, 193.27.228.0/22, 193.27.236.0/22, 193.27.240.0/22, 193.28.6.0/24, 193.28.44.0/24, 193.28.46.0/24, 193.28.186.0/24, 193.28.234.0/24, 193.29.19.0/24, 193.29.32.0/22, 193.30.39.0/24, 193.30.108.0/24, 193.30.236.0/22, 193.31.0.0/24, 193.31.192.0/21, 193.32.20.0/24, 193.32.60.0/22, 193.32.176.0/22, 193.32.190.0/23, 193.32.196.0/22, 193.32.200.0/22, 193.32.216.0/22, 193.33.10.0/23, 193.33.26.0/23, 193.33.62.0/23, 193.33.68.0/23, 193.33.74.0/23, 193.33.80.0/22, 193.33.84.0/23, 193.33.88.0/23, 193.33.100.0/23, 193.33.122.0/23, 193.33.124.0/24, 193.33.132.0/23, 193.33.144.0/23, 193.33.153.0/24, 193.33.160.0/23, 193.33.166.0/24, 193.33.184.0/23, 193.33.192.0/23, 193.33.208.0/24, 193.33.226.0/23, 193.33.230.0/23, 193.33.232.0/23, 193.33.240.0/23, 193.34.8.0/21, 193.34.124.0/22, 193.34.128.0/23, 193.34.160.0/23, 193.34.164.0/23, 193.34.184.0/23, 193.34.190.0/23, 193.34.200.128/25, 193.34.203.0/25, 193.34.208.0/22, 193.35.32.0/22, 193.35.38.0/23, 193.35.42.0/23, 193.35.48.0/22, 193.35.96.0/23, 193.35.100.0/24, 193.35.109.0/24, 193.35.227.0/24, 193.36.35.0/24, 193.36.41.0/24, 193.36.56.0/22, 193.37.36.0/24, 193.37.68.0/22, 193.37.134.0/24, 193.37.137.0/24, 193.37.141.0/24, 193.37.155.0/24, 193.37.157.0/24, 193.37.224.0/24, 193.37.229.0/24, 193.38.228.0/22, 193.38.232.0/22, 193.39.116.0/24, 193.39.160.0/22, 193.39.168.0/22, 193.39.220.0/22, 193.39.248.0/23, 193.41.76.0/22, 193.41.93.0/24, 193.41.140.0/22, 193.41.203.0/24, 193.42.116.0/23, 193.42.124.0/22, 193.42.142.0/24, 193.42.145.0/24, 193.43.8.0/22, 193.43.41.0/24, 193.43.72.0/24, 193.43.78.0/23, 193.43.91.0/24, 193.43.94.0/24, 193.43.127.0/24, 193.43.146.0/23, 193.43.180.0/24, 193.43.182.0/24, 193.43.232.0/22, 193.43.248.0/23, 193.43.252.0/22, 193.46.2.0/24, 193.46.56.0/23, 193.46.60.0/24, 193.46.65.0/24, 193.46.77.0/24, 193.46.88.0/24, 193.46.207.0/24, 193.46.208.0/24, 193.46.213.0/24, 193.46.244.0/22, 193.47.35.0/24, 193.47.41.0/24, 193.47.42.0/23, 193.47.44.0/22, 193.47.87.0/24, 193.47.146.0/24, 193.47.148.0/24, 193.47.154.0/24, 193.47.166.0/24, 193.47.240.0/22, 193.56.30.0/24, 193.56.64.0/22, 193.56.72.0/22, 193.56.188.0/24, 193.56.190.0/24, 193.56.202.0/24, 193.57.136.0/22, 193.58.179.0/24, 193.58.251.0/24, 193.84.8.0/24, 193.84.31.0/24, 193.84.71.0/24, 193.84.76.0/24, 193.84.78.0/24, 193.84.85.0/24, 193.84.88.0/24, 193.84.90.0/24, 193.84.113.0/24, 193.84.180.0/24, 193.84.187.0/24, 193.84.244.0/24, 193.93.28.0/22, 193.93.60.0/23, 193.93.120.0/22, 193.93.192.0/22, 193.93.232.0/21, 193.93.244.0/22, 193.104.11.0/24, 193.104.14.0/24, 193.104.24.0/24, 193.104.28.0/24, 193.104.59.0/24, 193.104.64.0/24, 193.104.69.0/24, 193.104.70.0/24, 193.104.87.0/24, 193.104.97.0/24, 193.104.102.0/24, 193.104.127.0/24, 193.104.128.0/24, 193.104.139.0/24, 193.104.145.0/24, 193.104.149.0/24, 193.104.191.0/24, 193.104.195.0/24, 193.104.207.0/24, 193.104.224.0/22, 193.104.230.0/24, 193.104.234.0/24, 193.104.237.0/24, 193.104.241.0/24, 193.104.243.0/24, 193.104.248.0/24, 193.105.4.0/24, 193.105.11.0/24, 193.105.27.0/24, 193.105.37.0/24, 193.105.39.0/24, 193.105.40.0/24, 193.105.53.0/24, 193.105.59.0/24, 193.105.65.0/24, 193.105.92.0/23, 193.105.94.0/24, 193.105.100.0/24, 193.105.107.0/24, 193.105.114.0/24, 193.105.118.0/24, 193.105.126.0/24, 193.105.131.0/24, 193.105.156.0/24, 193.105.169.0/24, 193.105.175.0/24, 193.105.194.0/24, 193.105.198.0/24, 193.105.213.0/24, 193.105.222.0/24, 193.105.235.0/24, 193.105.236.0/24, 193.105.255.0/24, 193.106.0.0/21, 193.106.40.0/22, 193.106.68.0/22, 193.106.72.0/22, 193.106.88.0/21, 193.106.96.0/23, 193.106.108.0/22, 193.106.112.0/22, 193.106.132.0/22, 193.106.150.0/23, 193.106.160.0/22, 193.106.168.0/21, 193.106.184.0/22, 193.106.191.0/24, 193.106.212.0/22, 193.106.232.0/22, 193.106.236.0/23, 193.106.240.0/22, 193.107.0.0/22, 193.107.52.0/22, 193.107.60.0/22, 193.107.92.0/22, 193.107.100.0/22, 193.107.140.0/22, 193.107.148.0/22, 193.107.152.0/22, 193.107.180.0/22, 193.107.188.0/22, 193.107.192.0/22, 193.107.228.0/22, 193.107.232.0/21, 193.108.182.0/23, 193.108.198.0/24, 193.108.208.0/24, 193.108.211.0/24, 193.109.60.0/24, 193.109.70.0/23, 193.109.76.0/23, 193.109.89.0/24, 193.109.114.0/24, 193.109.160.0/22, 193.109.217.0/24, 193.110.0.0/22, 193.110.72.0/22, 193.110.91.0/24, 193.110.93.0/24, 193.110.131.0/24, 193.110.136.0/24, 193.110.141.0/24, 193.110.174.0/23, 193.110.236.0/22, 193.111.2.0/23, 193.111.45.0/24, 193.111.80.0/24, 193.111.86.0/24, 193.111.156.0/22, 193.111.160.0/24, 193.111.176.0/22, 193.111.251.0/24, 193.111.252.0/22, 193.124.48.0/24, 193.124.54.0/24, 193.124.64.0/22, 193.124.116.0/24, 193.124.128.0/22, 193.124.134.0/24, 193.135.12.0/23, 193.138.70.0/24, 193.138.79.0/24, 193.138.82.0/24, 193.138.84.0/24, 193.138.89.0/24, 193.138.128.0/22, 193.138.148.0/22, 193.138.176.0/22, 193.138.232.0/22, 193.141.53.0/24, 193.141.230.0/23, 193.142.20.0/23, 193.142.30.0/23, 193.142.56.0/23, 193.142.114.0/24, 193.142.144.0/24, 193.142.148.0/24, 193.142.152.0/24, 193.142.156.0/24, 193.142.218.0/24, 193.142.246.0/24, 193.143.0.0/23, 193.143.64.0/22, 193.143.92.0/24, 193.143.96.0/23, 193.143.119.0/24, 193.143.250.0/23, 193.148.15.0/24, 193.148.44.0/24, 193.148.52.0/22, 193.148.62.0/24, 193.150.0.0/23, 193.150.20.0/24, 193.150.32.0/23, 193.150.52.0/22, 193.150.65.0/24, 193.150.70.0/23, 193.150.80.0/22, 193.150.92.0/22, 193.150.100.0/22, 193.150.105.0/24, 193.150.107.0/24, 193.150.108.0/22, 193.150.114.0/23, 193.150.116.0/22, 193.150.124.0/23, 193.151.0.0/22, 193.151.24.0/22, 193.151.108.0/22, 193.151.168.0/22, 193.151.192.0/19, 193.151.225.0/24, 193.151.227.0/24, 193.151.228.0/22, 193.151.232.0/21, 193.151.245.0/24, 193.160.12.0/23, 193.160.158.0/24, 193.160.204.0/23, 193.160.228.0/22, 193.160.255.0/24, 193.161.193.0/24, 193.161.204.0/23, 193.161.212.0/22, 193.161.246.0/23, 193.162.30.0/24, 193.162.130.0/24, 193.162.139.0/24, 193.163.3.0/24, 193.163.55.0/24, 193.163.94.0/24, 193.163.175.0/24, 193.163.197.0/24, 193.163.205.0/24, 193.164.16.0/22, 193.164.130.0/24, 193.164.146.0/24, 193.164.149.0/24, 193.164.150.0/24, 193.164.152.0/24, 193.164.194.0/23, 193.164.200.0/23, 193.164.204.0/23, 193.164.214.0/23, 193.168.3.0/24, 193.168.46.0/23, 193.168.48.0/23, 193.168.128.0/23, 193.168.130.0/24, 193.168.136.0/22, 193.168.176.0/22, 193.168.188.0/22, 193.168.217.0/24, 193.169.0.0/23, 193.169.4.0/23, 193.169.22.0/23, 193.169.24.0/23, 193.169.28.0/23, 193.169.32.0/21, 193.169.44.0/23, 193.169.52.0/23, 193.169.60.0/22, 193.169.96.0/23, 193.169.102.0/23, 193.169.110.0/23, 193.169.118.0/23, 193.169.122.0/23, 193.169.128.0/23, 193.169.172.0/23, 193.169.176.0/22, 193.169.202.0/23, 193.169.213.0/24, 193.169.220.0/22, 193.169.226.0/23, 193.169.228.0/23, 193.169.248.0/23, 193.176.76.0/22, 193.176.80.0/22, 193.176.92.0/22, 193.176.145.0/24, 193.176.153.0/24, 193.176.154.0/23, 193.176.156.0/23, 193.176.158.0/24, 193.176.179.0/24, 193.176.190.0/24, 193.176.227.0/24, 193.176.233.0/24, 193.178.118.0/24, 193.178.120.0/22, 193.178.131.0/24, 193.178.135.0/24, 193.178.158.0/23, 193.178.170.0/24, 193.178.210.0/24, 193.178.215.0/24, 193.186.14.0/24, 193.186.34.0/24, 193.187.73.0/24, 193.187.80.0/22, 193.187.96.0/24, 193.187.156.0/22, 193.187.172.0/22, 193.187.192.0/22, 193.188.20.0/22, 193.188.200.0/24, 193.188.251.0/24, 193.188.252.0/24, 193.188.254.0/24, 193.189.68.0/23, 193.189.120.0/23, 193.189.138.0/24, 193.189.145.0/24, 193.192.36.0/23, 193.192.128.0/19, 193.193.160.0/24, 193.193.186.0/23, 193.194.96.0/22, 193.194.100.0/23, 193.194.105.0/24, 193.194.153.0/24, 193.194.154.0/24, 193.200.10.0/24, 193.200.12.0/23, 193.200.18.0/24, 193.200.21.0/24, 193.200.22.0/24, 193.200.34.0/23, 193.200.54.0/23, 193.200.72.0/22, 193.200.88.0/23, 193.200.94.0/23, 193.200.129.0/24, 193.200.161.0/24, 193.200.168.0/24, 193.200.183.0/24, 193.200.189.0/24, 193.200.211.0/24, 193.200.239.0/24, 193.200.250.0/24, 193.201.8.0/24, 193.201.30.0/23, 193.201.88.0/22, 193.201.98.0/24, 193.201.111.0/24, 193.201.115.0/24, 193.201.118.0/23, 193.201.152.0/25, 193.201.156.0/25, 193.201.228.0/22, 193.203.40.0/22, 193.203.100.0/22, 193.203.112.0/23, 193.203.124.0/23, 193.218.136.0/22, 193.218.180.0/22, 193.218.189.0/24, 193.218.190.0/23, 193.219.104.0/24, 193.219.117.0/24, 193.219.125.0/24, 193.219.127.0/24, 193.221.200.0/22, 193.221.212.0/22, 193.222.48.0/23, 193.222.58.0/24, 193.222.109.0/24, 193.222.191.0/24, 193.222.251.0/24, 193.223.68.0/23, 193.223.103.0/24, 193.223.108.0/24, 193.227.98.0/24, 193.227.134.0/24, 193.227.232.0/23, 193.227.240.0/23, 193.227.252.0/23, 193.228.108.0/22, 193.228.128.0/24, 193.228.132.0/22, 193.228.146.0/24, 193.228.158.0/23, 193.228.160.0/21, 193.228.226.0/24, 193.228.231.0/24, 193.228.235.0/24, 193.232.0.0/15, 193.238.45.0/24, 193.238.46.0/23, 193.238.48.0/22, 193.238.72.0/22, 193.238.108.0/22, 193.238.116.0/22, 193.238.128.0/22, 193.238.176.0/22, 193.238.192.0/22, 193.239.24.0/22, 193.239.133.0/24, 193.239.229.0/24, 193.239.233.0/24, 193.239.242.0/23, 193.242.99.0/24, 193.242.105.0/24, 193.242.106.0/24, 193.242.109.0/24, 193.242.128.0/22, 193.242.132.0/23, 193.242.135.0/24, 193.242.145.0/24, 193.242.148.0/22, 193.242.153.0/24, 193.242.170.0/23, 193.242.173.0/24, 193.242.176.0/22, 193.242.190.0/23, 193.242.193.0/24, 193.242.200.0/21, 193.243.138.0/23, 193.243.161.0/24, 193.243.166.0/24, 193.243.172.0/22, 193.243.182.0/24, 193.246.51.0/24, 193.246.144.0/24, 193.246.150.0/24, 193.246.153.0/24, 193.246.159.0/24, 193.246.162.0/24, 193.246.167.0/24, 193.246.206.0/24, 193.246.245.0/24, 193.247.73.0/24, 193.247.213.0/24, 193.247.216.0/24, 193.247.236.0/23, 193.254.0.0/24, 193.254.3.0/24, 193.254.16.0/23, 193.254.208.0/23, 193.254.224.0/22, 193.254.248.0/23, 194.0.68.0/22, 194.0.112.0/22, 194.0.120.0/22, 194.0.144.0/23, 194.0.162.0/24, 194.0.209.0/24, 194.0.219.0/24, 194.1.145.0/24, 194.1.156.0/24, 194.1.161.0/24, 194.1.168.0/24, 194.1.170.0/23, 194.1.198.0/24, 194.1.202.0/24, 194.1.213.0/24, 194.1.214.0/24, 194.1.232.0/21, 194.1.240.0/22, 194.1.248.0/22, 194.4.48.0/22, 194.4.148.0/22, 194.5.60.0/23, 194.5.78.0/23, 194.5.92.0/22, 194.6.196.0/22, 194.6.216.0/21, 194.8.47.0/24, 194.8.55.0/24, 194.8.70.0/23, 194.8.72.0/23, 194.8.84.0/23, 194.8.128.0/22, 194.8.136.0/22, 194.8.152.0/22, 194.8.160.0/19, 194.8.224.0/23, 194.8.228.0/22, 194.8.232.0/22, 194.8.246.0/23, 194.9.26.0/23, 194.9.38.0/23, 194.9.69.0/24, 194.9.171.0/24, 194.9.224.0/20, 194.11.28.0/23, 194.12.48.0/20, 194.12.96.0/19, 194.13.143.0/24, 194.15.116.0/22, 194.15.126.0/23, 194.24.172.0/23, 194.24.190.0/23, 194.24.240.0/23, 194.26.17.0/24, 194.26.25.0/24, 194.26.27.0/24, 194.26.29.0/24, 194.26.67.0/24, 194.26.69.0/24, 194.26.116.0/24, 194.26.207.0/24, 194.26.209.0/24, 194.26.210.0/24, 194.26.228.0/23, 194.28.20.0/22, 194.28.28.0/22, 194.28.36.0/22, 194.28.56.0/21, 194.28.72.0/22, 194.28.88.0/21, 194.28.140.0/22, 194.28.152.0/22, 194.28.160.0/22, 194.28.184.0/22, 194.28.192.0/22, 194.28.206.0/23, 194.28.208.0/21, 194.28.216.0/22, 194.28.224.0/22, 194.28.236.0/22, 194.28.240.0/22, 194.30.190.0/24, 194.31.9.0/24, 194.31.12.0/23, 194.31.66.0/23, 194.31.109.0/24, 194.31.152.0/22, 194.31.172.0/22, 194.32.86.0/23, 194.32.156.0/22, 194.32.243.0/24, 194.32.252.0/22, 194.33.15.0/24, 194.33.20.0/22, 194.33.79.0/24, 194.33.81.0/24, 194.33.84.0/22, 194.33.92.0/22, 194.33.97.0/24, 194.33.110.0/24, 194.34.96.0/22, 194.34.237.0/24, 194.34.239.0/24, 194.35.44.0/22, 194.35.48.0/22, 194.35.114.0/23, 194.35.116.0/22, 194.36.16.0/23, 194.36.148.0/22, 194.36.161.0/24, 194.36.170.0/23, 194.36.196.0/22, 194.36.208.0/23, 194.37.1.0/24, 194.37.254.0/24, 194.38.0.0/22, 194.38.8.0/22, 194.39.99.0/24, 194.39.101.0/24, 194.39.249.0/24, 194.41.52.0/22, 194.48.94.0/24, 194.48.96.0/22, 194.48.218.0/24, 194.49.50.0/23, 194.49.120.0/22, 194.49.127.0/24, 194.50.6.0/24, 194.50.9.0/24, 194.50.10.0/24, 194.50.12.0/22, 194.50.24.0/24, 194.50.72.0/24, 194.50.87.0/24, 194.50.103.0/24, 194.50.104.0/24, 194.50.116.0/24, 194.50.120.0/24, 194.50.127.0/24, 194.50.128.0/22, 194.50.139.0/24, 194.50.141.0/24, 194.50.142.0/23, 194.50.148.0/23, 194.50.150.0/24, 194.50.152.0/24, 194.50.169.0/24, 194.50.170.0/23, 194.50.180.0/24, 194.50.190.0/24, 194.50.255.0/24, 194.53.175.0/24, 194.53.184.0/22, 194.54.12.0/22, 194.54.64.0/22, 194.54.132.0/22, 194.54.148.0/22, 194.54.152.0/22, 194.54.176.0/22, 194.55.140.0/22, 194.55.161.0/24, 194.55.163.0/24, 194.55.164.0/22, 194.55.168.0/24, 194.55.171.0/24, 194.55.234.0/23, 194.55.236.0/22, 194.55.244.0/23, 194.58.62.0/23, 194.58.70.0/23, 194.58.78.0/23, 194.58.88.0/21, 194.58.96.0/19, 194.58.160.0/19, 194.59.184.0/24, 194.59.186.0/24, 194.60.236.0/22, 194.60.242.0/24, 194.60.246.0/23, 194.60.248.0/23, 194.61.24.0/23, 194.61.60.0/22, 194.61.67.0/24, 194.62.0.0/24, 194.63.156.0/22, 194.67.0.0/18, 194.67.64.0/20, 194.67.84.0/22, 194.67.88.0/21, 194.67.96.0/19, 194.67.128.0/18, 194.67.224.0/19, 194.76.36.0/23, 194.76.46.0/23, 194.76.220.0/23, 194.76.236.0/23, 194.79.4.0/22, 194.79.32.0/22, 194.79.44.0/22, 194.79.250.0/23, 194.79.255.0/24, 194.84.0.0/16, 194.85.0.0/17, 194.85.128.0/18, 194.85.192.0/19, 194.85.224.0/20, 194.85.240.0/21, 194.85.252.0/22, 194.88.14.0/23, 194.88.138.0/23, 194.88.140.0/23, 194.88.198.0/23, 194.88.200.0/23, 194.88.204.0/23, 194.88.208.0/22, 194.99.116.0/24, 194.104.8.0/22, 194.104.224.0/23, 194.105.130.0/23, 194.105.148.0/24, 194.105.156.0/23, 194.105.192.0/19, 194.106.194.0/23, 194.107.17.0/24, 194.107.21.0/24, 194.107.23.0/24, 194.107.80.0/24, 194.107.124.0/24, 194.107.200.0/24, 194.110.8.0/22, 194.110.18.0/24, 194.110.52.0/23, 194.110.66.0/24, 194.110.71.0/24, 194.110.80.0/22, 194.110.152.0/23, 194.110.202.0/23, 194.110.223.0/24, 194.110.246.0/23, 194.110.253.0/24, 194.113.34.0/23, 194.113.104.0/22, 194.113.139.0/24, 194.113.140.0/24, 194.113.142.0/24, 194.113.155.0/24, 194.113.209.0/24, 194.113.219.0/24, 194.113.232.0/21, 194.113.245.0/24, 194.114.128.0/22, 194.116.162.0/23, 194.116.169.0/24, 194.116.172.0/23, 194.116.190.0/23, 194.116.194.0/23, 194.116.214.0/23, 194.116.216.0/23, 194.117.50.128/25, 194.117.65.0/24, 194.117.70.0/24, 194.117.89.0/24, 194.117.250.0/23, 194.120.24.0/24, 194.120.104.0/23, 194.120.116.0/24, 194.124.36.0/22, 194.125.224.0/22, 194.125.236.0/22, 194.125.254.0/23, 194.126.160.0/22, 194.126.168.0/22, 194.126.203.0/24, 194.127.112.0/22, 194.135.4.0/22, 194.135.8.0/21, 194.135.21.0/24, 194.135.23.0/24, 194.135.24.0/24, 194.135.40.0/22, 194.135.52.0/22, 194.135.60.0/22, 194.135.64.0/21, 194.135.72.0/22, 194.135.96.0/22, 194.135.100.0/24, 194.135.103.0/24, 194.135.108.0/22, 194.135.112.0/22, 194.135.120.0/22, 194.135.135.0/24, 194.135.136.0/21, 194.135.144.0/21, 194.135.192.0/22, 194.135.208.0/20, 194.135.224.0/20, 194.135.240.0/21, 194.140.228.0/24, 194.140.247.0/24, 194.143.138.0/23, 194.143.148.0/22, 194.145.158.0/24, 194.145.168.0/22, 194.146.113.0/24, 194.146.119.0/24, 194.146.200.0/22, 194.146.240.0/22, 194.147.32.0/22, 194.147.48.0/22, 194.147.114.0/23, 194.147.148.0/23, 194.147.224.0/24, 194.147.246.0/23, 194.147.249.0/24, 194.149.64.0/21, 194.149.90.0/24, 194.149.95.0/24, 194.149.224.0/22, 194.149.234.0/23, 194.149.242.0/24, 194.150.88.0/22, 194.150.128.0/19, 194.150.174.0/23, 194.150.179.0/24, 194.150.202.0/23, 194.150.214.0/23, 194.150.222.0/23, 194.150.234.0/23, 194.150.254.0/23, 194.152.34.0/23, 194.152.62.0/23, 194.153.112.0/24, 194.153.158.128/25, 194.154.64.0/19, 194.156.100.0/22, 194.156.116.0/22, 194.156.122.0/24, 194.156.124.0/23, 194.156.166.0/23, 194.156.184.0/23, 194.156.200.0/23, 194.156.204.0/23, 194.165.0.0/23, 194.165.18.0/23, 194.165.20.0/22, 194.165.30.0/23, 194.165.50.0/24, 194.165.57.0/24, 194.165.61.0/24, 194.169.56.0/22, 194.169.192.0/24, 194.169.247.0/24, 194.169.250.0/24, 194.169.255.0/24, 194.176.100.0/24, 194.176.114.0/24, 194.176.118.0/24, 194.177.20.0/22, 194.180.226.192/26, 194.186.0.0/16, 194.187.29.0/24, 194.187.120.0/22, 194.187.148.0/22, 194.187.196.0/22, 194.187.204.0/22, 194.190.0.0/16, 194.213.12.0/24, 194.213.21.0/24, 194.213.96.0/23, 194.213.102.0/23, 194.213.110.0/23, 194.213.116.0/23, 194.213.122.0/23, 194.226.0.0/16, 194.242.4.0/22, 194.242.8.0/23, 194.242.26.0/23, 194.242.30.0/23, 194.242.120.0/21, 194.246.80.0/24, 194.246.92.0/22, 194.246.112.0/23, 194.247.14.0/23, 194.247.20.0/22, 194.247.24.0/23, 194.247.33.0/24, 194.247.40.0/24, 194.247.51.0/24, 194.247.53.0/24, 194.247.54.0/23, 194.247.128.0/19, 194.247.168.0/23, 194.247.184.0/23, 194.247.190.0/23, 195.2.64.0/20, 195.2.80.0/22, 195.2.84.0/23, 195.2.87.0/24, 195.2.88.0/22, 195.2.94.0/23, 195.2.192.0/22, 195.2.198.0/23, 195.2.204.0/22, 195.2.210.0/23, 195.2.214.0/23, 195.2.216.0/23, 195.2.226.0/23, 195.2.232.0/23, 195.2.238.0/23, 195.2.240.0/23, 195.3.132.0/22, 195.3.140.0/22, 195.3.180.0/24, 195.3.182.0/23, 195.3.216.0/22, 195.3.240.0/21, 195.3.252.0/22, 195.5.105.0/24, 195.5.108.0/23, 195.5.128.0/19, 195.5.178.0/24, 195.7.160.0/19, 195.8.34.0/23, 195.8.36.0/22, 195.8.44.0/23, 195.8.56.0/23, 195.9.0.0/16, 195.10.193.0/24, 195.10.198.0/24, 195.10.205.0/24, 195.10.214.0/24, 195.12.64.0/19, 195.13.50.0/24, 195.14.12.0/24, 195.14.32.0/19, 195.14.96.0/23, 195.14.100.0/23, 195.14.106.0/23, 195.14.108.0/23, 195.14.114.0/23, 195.16.32.0/19, 195.16.92.0/23, 195.16.95.0/24, 195.16.96.0/19, 195.18.12.0/22, 195.18.20.0/22, 195.18.26.0/24, 195.18.32.0/19, 195.19.0.0/16, 195.20.18.0/23, 195.20.28.0/22, 195.20.102.0/23, 195.20.118.0/23, 195.20.128.0/24, 195.20.135.0/24, 195.20.140.0/24, 195.20.194.0/23, 195.20.196.0/23, 195.22.104.0/22, 195.22.123.0/24, 195.22.148.0/23, 195.22.152.0/23, 195.24.64.0/21, 195.24.228.0/23, 195.24.254.0/23, 195.26.30.0/23, 195.26.80.0/22, 195.26.160.0/19, 195.28.14.0/23, 195.28.32.0/19, 195.34.0.0/18, 195.34.192.0/22, 195.34.224.0/19, 195.35.68.0/22, 195.35.116.0/23, 195.38.23.0/24, 195.38.32.0/19, 195.39.206.0/23, 195.39.210.0/23, 195.39.228.0/23, 195.39.248.0/23, 195.42.64.0/19, 195.42.96.0/23, 195.42.118.0/23, 195.42.122.0/23, 195.42.146.0/23, 195.42.154.0/23, 195.42.156.0/23, 195.42.160.0/19, 195.43.32.0/22, 195.43.68.0/23, 195.43.76.0/23, 195.43.90.0/23, 195.43.92.0/22, 195.43.134.0/23, 195.43.144.0/24, 195.46.96.0/19, 195.46.160.0/19, 195.47.192.0/24, 195.47.196.0/24, 195.47.204.0/24, 195.49.168.0/22, 195.49.184.0/22, 195.49.192.0/20, 195.54.0.0/19, 195.54.32.0/23, 195.54.40.0/22, 195.54.176.0/23, 195.54.192.0/19, 195.58.0.0/19, 195.58.55.0/24, 195.58.56.0/24, 195.60.220.0/22, 195.60.244.0/22, 195.62.0.0/22, 195.62.6.0/23, 195.62.34.0/24, 195.62.38.0/23, 195.62.48.0/23, 195.62.52.0/22, 195.62.58.0/23, 195.62.60.0/22, 195.62.70.0/23, 195.62.78.0/23, 195.62.92.0/23, 195.64.101.0/24, 195.64.117.0/24, 195.64.140.0/22, 195.64.156.0/23, 195.64.192.0/19, 195.66.67.0/24, 195.66.71.0/24, 195.66.72.0/24, 195.66.77.0/24, 195.66.78.0/24, 195.66.84.0/24, 195.66.89.0/24, 195.66.108.0/24, 195.66.117.0/24, 195.66.126.0/24, 195.66.138.0/23, 195.68.128.0/18, 195.68.240.0/23, 195.68.245.0/24, 195.68.252.0/23, 195.69.64.0/22, 195.69.79.0/24, 195.69.136.0/22, 195.69.151.0/24, 195.69.156.0/22, 195.69.160.0/24, 195.69.168.0/22, 195.69.200.0/21, 195.69.216.0/22, 195.69.236.0/22, 195.69.248.0/22, 195.70.192.0/19, 195.72.140.0/22, 195.72.224.0/19, 195.74.62.0/23, 195.74.74.0/24, 195.74.82.0/24, 195.74.84.0/24, 195.74.95.0/24, 195.78.60.0/23, 195.78.72.0/23, 195.78.96.0/23, 195.78.104.0/23, 195.78.116.0/23, 195.78.126.0/23, 195.80.48.0/22, 195.80.56.0/22, 195.80.136.0/24, 195.80.139.0/24, 195.80.144.0/22, 195.80.224.0/24, 195.80.228.0/24, 195.82.134.0/23, 195.82.136.0/23, 195.82.140.0/22, 195.82.144.0/23, 195.82.154.0/23, 195.82.156.0/23, 195.85.206.0/24, 195.85.213.0/24, 195.85.231.0/24, 195.85.234.0/23, 195.85.236.0/24, 195.85.238.0/24, 195.88.0.0/23, 195.88.14.0/23, 195.88.16.0/23, 195.88.22.0/23, 195.88.40.0/23, 195.88.52.0/23, 195.88.58.0/23, 195.88.62.0/23, 195.88.68.0/23, 195.88.92.0/23, 195.88.102.0/23, 195.88.112.0/23, 195.88.120.0/23, 195.88.126.0/23, 195.88.132.0/22, 195.88.170.0/23, 195.88.178.0/23, 195.88.184.0/23, 195.88.192.0/23, 195.88.196.0/24, 195.88.198.0/23, 195.88.200.0/23, 195.88.224.0/23, 195.88.240.0/23, 195.88.248.0/23, 195.88.252.0/23, 195.90.122.0/23, 195.90.128.0/18, 195.91.128.0/17, 195.93.128.0/23, 195.93.144.0/23, 195.93.148.0/22, 195.93.160.0/23, 195.93.164.0/23, 195.93.180.0/23, 195.93.186.0/23, 195.93.188.0/23, 195.93.206.0/23, 195.93.228.0/23, 195.93.240.0/23, 195.93.244.0/24, 195.93.246.0/23, 195.93.248.0/23, 195.93.252.0/23, 195.94.224.0/19, 195.95.133.0/24, 195.95.212.0/22, 195.95.218.0/24, 195.95.252.0/23, 195.96.40.0/21, 195.96.64.0/19, 195.96.160.0/19, 195.98.32.0/19, 195.98.64.0/19, 195.98.160.0/19, 195.110.6.0/23, 195.110.32.0/23, 195.110.46.0/23, 195.110.52.0/23, 195.110.62.0/23, 195.112.96.0/19, 195.112.224.0/19, 195.114.2.0/23, 195.114.20.0/23, 195.114.104.0/23, 195.114.112.0/24, 195.114.116.0/22, 195.114.122.0/23, 195.122.224.0/19, 195.128.48.0/20, 195.128.64.0/19, 195.128.96.0/22, 195.128.120.0/21, 195.128.133.0/24, 195.128.137.0/24, 195.128.152.0/23, 195.128.157.0/24, 195.128.158.0/23, 195.128.244.0/23, 195.130.209.0/24, 195.130.214.0/24, 195.130.216.0/24, 195.131.0.0/16, 195.133.56.0/23, 195.133.60.0/23, 195.133.216.0/22, 195.133.224.0/19, 195.135.204.0/22, 195.135.212.0/22, 195.135.232.0/21, 195.135.240.0/22, 195.137.171.0/24, 195.137.187.0/24, 195.137.200.0/23, 195.138.206.0/24, 195.138.210.0/24, 195.138.232.0/21, 195.140.144.0/22, 195.140.180.0/22, 195.140.204.0/22, 195.140.216.0/24, 195.144.0.0/23, 195.144.6.0/24, 195.144.29.0/24, 195.144.192.0/18, 195.146.64.0/19, 195.149.89.0/24, 195.149.104.0/23, 195.149.110.0/23, 195.149.196.0/23, 195.149.200.0/21, 195.151.0.0/16, 195.158.220.0/22, 195.158.224.0/23, 195.158.228.0/23, 195.158.232.0/23, 195.158.250.0/23, 195.158.254.0/23, 195.160.164.0/23, 195.160.244.0/22, 195.161.0.0/16, 195.162.6.0/23, 195.162.8.0/22, 195.162.22.0/23, 195.162.32.0/19, 195.162.78.0/23, 195.166.160.0/19, 195.170.32.0/19, 195.170.174.0/24, 195.170.192.0/18, 195.177.104.0/22, 195.177.120.0/22, 195.177.194.0/23, 195.177.204.0/23, 195.177.226.0/23, 195.178.2.0/23, 195.178.16.0/23, 195.178.20.0/22, 195.178.24.0/23, 195.178.98.0/24, 195.178.108.0/23, 195.178.192.0/19, 195.182.3.0/24, 195.182.20.0/24, 195.182.28.0/24, 195.182.32.0/24, 195.182.57.0/24, 195.182.128.0/19, 195.184.78.0/23, 195.184.192.0/19, 195.189.16.0/22, 195.189.44.0/22, 195.189.100.0/22, 195.189.108.0/22, 195.189.134.0/23, 195.189.136.0/23, 195.189.180.0/23, 195.189.218.0/23, 195.189.222.0/23, 195.189.238.0/23, 195.190.12.0/23, 195.190.14.0/24, 195.190.96.0/19, 195.190.132.0/24, 195.191.4.0/23, 195.191.7.0/24, 195.191.10.0/23, 195.191.18.0/23, 195.191.32.0/23, 195.191.36.0/23, 195.191.50.0/23, 195.191.64.0/24, 195.191.76.0/22, 195.191.82.0/23, 195.191.88.0/23, 195.191.130.0/23, 195.191.136.0/23, 195.191.146.0/23, 195.191.158.0/23, 195.191.166.0/23, 195.191.174.0/23, 195.191.182.0/23, 195.191.190.0/23, 195.191.192.0/22, 195.191.208.0/23, 195.191.220.0/23, 195.191.226.0/23, 195.191.236.0/22, 195.191.242.0/23, 195.191.246.0/23, 195.200.203.0/24, 195.200.209.0/24, 195.200.213.0/24, 195.200.216.0/24, 195.200.223.0/24, 195.200.226.0/23, 195.200.236.0/23, 195.200.248.0/23, 195.206.32.0/19, 195.206.122.0/23, 195.206.226.0/23, 195.206.237.0/24, 195.206.240.0/23, 195.206.244.0/23, 195.206.254.0/23, 195.208.0.0/15, 195.210.2.0/23, 195.210.9.0/24, 195.210.20.0/23, 195.210.30.0/23, 195.210.128.0/18, 195.211.4.0/22, 195.211.20.0/22, 195.211.28.0/22, 195.211.36.0/22, 195.211.42.0/23, 195.211.64.0/22, 195.211.76.0/22, 195.211.80.0/22, 195.211.88.0/22, 195.211.100.0/22, 195.211.104.0/22, 195.211.112.0/22, 195.211.120.0/22, 195.211.128.0/22, 195.211.160.0/22, 195.211.192.0/21, 195.211.204.0/22, 195.211.216.0/22, 195.211.232.0/21, 195.211.244.0/22, 195.211.248.0/23, 195.211.251.0/24, 195.214.236.0/22, 195.216.194.0/23, 195.216.200.0/23, 195.216.210.0/23, 195.216.212.0/23, 195.216.216.0/23, 195.216.241.0/24, 195.216.244.0/24, 195.218.128.0/17, 195.222.128.0/18, 195.225.38.0/23, 195.225.56.0/23, 195.225.96.0/24, 195.225.108.0/22, 195.225.160.0/22, 195.225.233.0/24, 195.226.203.0/24, 195.226.204.0/24, 195.226.209.0/24, 195.226.222.0/24, 195.230.64.0/19, 195.230.99.0/24, 195.230.101.0/24, 195.230.109.0/24, 195.230.111.0/24, 195.230.112.0/24, 195.234.2.0/23, 195.234.52.0/24, 195.234.56.0/24, 195.234.90.0/23, 195.234.96.0/23, 195.234.130.0/24, 195.234.140.0/24, 195.234.143.0/24, 195.234.154.0/24, 195.234.170.0/24, 195.234.190.0/24, 195.234.200.0/22, 195.234.208.0/22, 195.238.72.0/23, 195.238.100.0/22, 195.238.112.0/22, 195.238.230.0/24, 195.238.239.0/24, 195.238.246.0/24, 195.238.250.0/24, 195.239.0.0/16, 195.242.82.0/23, 195.242.142.0/23, 195.242.144.0/23, 195.242.147.0/24, 195.242.216.0/23, 195.244.4.0/23, 195.244.24.0/23, 195.245.76.0/23, 195.245.205.0/24, 195.245.206.0/24, 195.245.211.0/24, 195.245.214.0/24, 195.245.232.0/24, 195.245.244.0/24, 195.245.249.0/24, 195.246.106.0/23, 195.246.118.0/23, 195.246.122.0/23, 195.246.124.0/23, 195.246.218.0/24, 195.246.246.0/23, 195.246.248.0/23, 195.246.254.0/23, 195.248.69.0/24, 195.248.71.0/24, 195.248.81.0/24, 195.248.82.0/24, 195.248.94.0/24, 195.248.232.0/23, 195.248.236.0/23, 195.248.244.0/23, 195.248.248.0/23, 195.250.42.0/24, 195.250.51.0/24, 195.250.56.0/24, 195.254.188.0/23, 196.1.5.0/24, 203.81.208.0/20, 212.0.64.0/19, 212.1.224.0/19, 212.3.128.0/19, 212.5.64.0/18, 212.5.160.0/19, 212.5.224.0/19, 212.6.0.0/19, 212.6.56.0/24, 212.7.96.0/19, 212.7.224.0/19, 212.8.224.0/22, 212.8.228.0/23, 212.8.232.0/21, 212.8.247.0/24, 212.11.128.0/19, 212.12.0.0/19, 212.12.64.0/19, 212.13.0.0/19, 212.13.96.0/19, 212.14.160.0/19, 212.14.192.0/19, 212.15.32.0/19, 212.15.96.0/19, 212.16.0.0/19, 212.16.192.0/19, 212.17.0.0/19, 212.18.96.0/24, 212.18.117.0/24, 212.18.118.0/24, 212.19.0.0/19, 212.20.0.0/18, 212.21.0.0/19, 212.22.64.0/19, 212.23.64.0/19, 212.24.32.0/19, 212.24.115.0/24, 212.26.224.0/19, 212.30.128.0/18, 212.32.192.0/19, 212.33.0.0/19, 212.33.224.0/19, 212.34.32.0/19, 212.34.96.0/19, 212.35.160.0/19, 212.36.224.0/19, 212.38.96.0/19, 212.40.192.0/19, 212.41.0.0/18, 212.42.32.0/19, 212.44.64.0/19, 212.44.128.0/19, 212.45.0.0/19, 212.45.224.0/19, 212.46.0.0/19, 212.46.47.0/24, 212.46.54.0/24, 212.46.192.0/18, 212.47.32.0/24, 212.48.32.0/19, 212.48.128.0/19, 212.48.192.0/19, 212.48.224.0/20, 212.49.96.0/19, 212.53.32.0/19, 212.55.96.0/19, 212.57.96.0/19, 212.57.128.0/18, 212.58.192.0/19, 212.59.96.0/19, 212.60.20.0/22, 212.65.64.0/19, 212.66.32.0/20, 212.66.56.0/21, 212.67.0.0/19, 212.69.96.0/19, 212.73.96.0/19, 212.74.192.0/18, 212.75.128.0/19, 212.75.192.0/18, 212.76.128.0/18, 212.77.128.0/19, 212.83.0.0/19, 212.86.99.0/24, 212.90.96.0/22, 212.91.192.0/19, 212.92.96.0/22, 212.92.100.0/23, 212.92.128.0/18, 212.94.0.0/19, 212.94.96.0/19, 212.96.96.0/19, 212.96.192.0/19, 212.100.128.0/19, 212.103.96.0/19, 212.104.64.0/19, 212.104.131.0/24, 212.106.32.0/19, 212.107.192.0/18, 212.108.109.0/24, 212.109.0.0/19, 212.109.168.0/21, 212.109.192.0/19, 212.110.156.0/22, 212.111.64.0/20, 212.111.83.0/24, 212.111.90.0/23, 212.111.92.0/22, 212.113.96.0/19, 212.113.224.0/19, 212.115.51.0/24, 212.115.56.0/22, 212.116.96.0/19, 212.118.32.0/22, 212.118.44.0/22, 212.118.48.0/22, 212.118.56.0/24, 212.118.60.0/24, 212.118.62.0/23, 212.119.96.0/19, 212.119.160.0/19, 212.119.192.0/18, 212.120.160.0/19, 212.122.0.0/19, 212.124.0.0/19, 212.124.96.0/19, 212.129.96.0/19, 212.152.32.0/19, 212.158.160.0/20, 212.162.136.0/22, 212.164.0.0/16, 212.176.0.0/16, 212.188.0.0/17, 212.192.32.0/19, 212.192.64.0/18, 212.192.128.0/18, 212.192.192.0/20, 212.192.224.0/20, 212.192.246.0/23, 212.192.248.0/23, 212.193.10.0/23, 212.193.14.0/23, 212.193.32.0/19, 212.193.64.0/18, 212.193.128.0/17, 212.220.0.0/16, 212.232.0.0/20, 212.232.32.0/19, 212.233.64.0/21, 212.233.80.0/21, 212.233.100.0/22, 212.233.104.0/21, 212.233.112.0/21, 212.233.124.0/22, 212.248.0.0/17, 213.5.16.0/22, 213.5.24.0/21, 213.5.48.0/21, 213.5.73.0/24, 213.5.74.0/23, 213.5.76.0/22, 213.5.80.0/21, 213.5.96.0/20, 213.5.120.0/21, 213.5.135.0/24, 213.5.160.0/21, 213.5.216.0/21, 213.5.224.0/23, 213.5.227.0/24, 213.5.228.0/22, 213.5.240.0/21, 213.24.0.0/16, 213.27.0.0/17, 213.33.128.0/17, 213.59.0.0/18, 213.59.64.0/19, 213.59.96.0/20, 213.59.128.0/17, 213.79.0.0/19, 213.79.64.0/18, 213.80.128.0/17, 213.85.0.0/16, 213.87.0.0/16, 213.88.0.0/17, 213.108.0.0/22, 213.108.16.0/22, 213.108.32.0/21, 213.108.128.0/23, 213.108.130.0/24, 213.108.132.0/23, 213.108.135.0/24, 213.108.136.0/21, 213.108.144.0/21, 213.108.168.0/21, 213.108.184.0/21, 213.108.200.0/21, 213.108.208.0/20, 213.108.248.0/21, 213.109.0.0/19, 213.109.48.0/20, 213.109.72.0/24, 213.109.74.0/23, 213.109.78.0/23, 213.109.145.0/24, 213.109.201.0/24, 213.109.203.0/24, 213.110.0.0/18, 213.110.192.0/19, 213.110.224.0/22, 213.110.228.0/23, 213.110.232.0/22, 213.110.236.0/23, 213.110.240.0/20, 213.128.0.0/19, 213.128.192.0/19, 213.129.32.0/19, 213.129.96.0/19, 213.130.80.0/21, 213.131.0.0/19, 213.132.64.0/19, 213.134.6.0/24, 213.134.8.0/24, 213.134.18.0/24, 213.134.192.0/19, 213.135.64.0/18, 213.135.128.0/19, 213.137.224.0/19, 213.138.64.0/19, 213.138.192.0/20, 213.138.208.0/21, 213.138.220.0/22, 213.139.208.0/22, 213.139.229.0/24, 213.139.248.0/22, 213.140.96.0/19, 213.140.224.0/19, 213.141.128.0/19, 213.141.224.0/19, 213.142.32.0/19, 213.142.138.0/23, 213.142.192.0/19, 213.145.0.0/18, 213.147.32.0/19, 213.147.192.0/19, 213.148.160.0/19, 213.149.0.0/19, 213.150.64.0/19, 213.151.0.0/19, 213.152.128.0/19, 213.154.160.0/19, 213.155.192.0/19, 213.156.128.0/22, 213.156.192.0/19, 213.158.0.0/19, 213.159.192.0/19, 213.165.192.0/19, 213.166.76.0/22, 213.167.32.0/19, 213.167.192.0/19, 213.168.32.0/19, 213.168.128.0/19, 213.170.64.0/18, 213.170.220.0/22, 213.171.0.0/18, 213.172.0.0/19, 213.176.224.0/19, 213.177.96.0/19, 213.177.183.0/24, 213.177.191.0/24, 213.178.32.0/19, 213.178.153.0/24, 213.178.154.0/24, 213.180.192.0/19, 213.181.0.0/19, 213.182.160.0/19, 213.183.96.0/19, 213.183.224.0/19, 213.184.128.0/19, 213.187.96.0/19, 213.189.192.0/18, 213.190.224.0/19, 213.191.0.0/19, 213.193.0.0/19, 213.208.160.0/21, 213.208.168.0/23, 213.208.170.0/24, 213.208.172.0/22, 213.208.176.0/20, 213.210.64.0/18, 213.217.0.0/23, 213.219.192.0/19, 213.219.224.0/20, 213.219.240.0/21, 213.219.248.0/22, 213.221.0.0/18, 213.222.224.0/19, 213.226.80.0/22, 213.228.64.0/18, 213.232.120.0/22, 213.232.192.0/24, 213.232.194.0/23, 213.232.199.0/24, 213.232.207.0/24, 213.232.208.0/21, 213.232.227.0/24, 213.232.228.0/23, 213.232.233.0/24, 213.232.235.0/24, 213.232.240.0/24, 213.232.249.0/24, 213.232.250.0/23, 213.232.254.0/24, 213.234.0.0/19, 213.234.192.0/18, 213.241.192.0/18, 213.242.0.0/18, 213.242.192.0/18, 213.243.64.0/18, 213.247.128.0/17, 213.248.0.0/18, 213.251.192.0/18, 213.252.64.0/18, 217.8.80.0/20, 217.8.124.0/22, 217.8.224.0/20, 217.9.80.0/20, 217.9.144.0/20, 217.10.32.0/20, 217.12.32.0/20, 217.12.64.0/20, 217.12.96.0/20, 217.12.240.0/20, 217.13.208.0/20, 217.14.16.0/20, 217.14.48.0/20, 217.14.192.0/20, 217.14.240.0/20, 217.15.16.0/20, 217.15.48.0/20, 217.15.128.0/19, 217.15.192.0/20, 217.16.16.0/20, 217.17.112.0/20, 217.17.160.0/19, 217.18.49.0/24, 217.18.50.0/24, 217.18.60.0/22, 217.18.128.0/19, 217.19.96.0/19, 217.20.64.0/19, 217.20.144.0/20, 217.21.96.0/20, 217.21.208.0/20, 217.22.9.0/24, 217.22.160.0/20, 217.23.16.0/20, 217.23.64.0/19, 217.23.128.0/20, 217.23.144.0/22, 217.23.148.0/24, 217.23.150.0/23, 217.23.152.0/21, 217.23.176.0/20, 217.24.112.0/20, 217.24.176.0/20, 217.25.80.0/20, 217.25.144.0/20, 217.25.208.0/20, 217.25.224.0/20, 217.26.0.0/19, 217.26.217.0/24, 217.27.128.0/20, 217.28.72.0/21, 217.28.131.0/24, 217.28.208.0/20, 217.28.224.0/20, 217.29.48.0/20, 217.29.80.0/20, 217.29.176.0/20, 217.30.240.0/20, 217.64.128.0/20, 217.64.144.0/22, 217.65.0.0/20, 217.65.79.0/24, 217.65.80.0/20, 217.65.208.0/20, 217.66.16.0/20, 217.66.64.0/19, 217.66.144.0/20, 217.67.0.0/20, 217.67.112.0/20, 217.67.176.0/20, 217.69.128.0/20, 217.69.192.0/19, 217.70.5.0/24, 217.70.16.0/20, 217.70.96.0/19, 217.71.128.0/20, 217.71.160.0/20, 217.71.224.0/20, 217.72.0.0/21, 217.72.8.0/22, 217.72.144.0/20, 217.73.56.0/21, 217.73.112.0/20, 217.73.192.0/20, 217.74.32.0/20, 217.74.112.0/20, 217.74.160.0/20, 217.74.240.0/20, 217.76.32.0/20, 217.76.176.0/20, 217.77.48.0/20, 217.77.96.0/22, 217.77.100.0/24, 217.77.102.0/24, 217.77.104.0/21, 217.78.176.0/20, 217.78.224.0/22, 217.78.228.0/24, 217.78.230.0/23, 217.78.232.0/23, 217.78.236.0/24, 217.79.0.0/19, 217.79.48.0/20, 217.79.224.0/20, 217.106.0.0/15, 217.112.0.0/19, 217.112.32.0/20, 217.113.112.0/20, 217.114.0.0/19, 217.114.144.0/20, 217.114.176.0/20, 217.114.224.0/19, 217.115.80.0/20, 217.115.176.0/20, 217.116.48.0/20, 217.116.128.0/19, 217.117.80.0/20, 217.117.112.0/20, 217.117.176.0/20, 217.117.240.0/20, 217.118.64.0/19, 217.118.176.0/20, 217.119.16.0/20, 217.119.80.0/20, 217.119.139.0/24, 217.144.96.0/21, 217.144.160.0/20, 217.145.16.0/20, 217.145.144.0/20, 217.145.224.0/22, 217.146.32.0/20, 217.147.8.0/22, 217.147.16.0/20, 217.148.48.0/20, 217.148.192.0/19, 217.149.16.0/20, 217.149.176.0/20, 217.150.0.0/18, 217.150.72.0/21, 217.150.192.0/20, 217.151.16.0/20, 217.151.64.0/20, 217.151.128.0/20, 217.151.224.0/20, 217.168.64.0/20, 217.168.240.0/21, 217.169.80.0/20, 217.170.64.0/19, 217.170.112.0/20, 217.170.208.0/20, 217.171.0.0/20, 217.171.146.0/23, 217.171.152.0/21, 217.172.16.0/20, 217.173.16.0/20, 217.173.64.0/20, 217.174.0.0/20, 217.174.96.0/20, 217.174.160.0/19, 217.175.0.0/19, 217.175.32.0/20, 217.175.128.0/19, 217.194.240.0/20, 217.195.64.0/19, 217.195.96.0/20, 217.195.208.0/20, 217.196.96.0/20, 217.197.0.0/20, 217.197.107.0/24, 217.197.112.0/20, 217.197.172.0/22, 217.197.192.0/20, 217.197.224.0/19, 217.198.0.0/20, 217.198.80.0/20, 217.198.160.0/20, 217.199.208.0/20, 217.199.224.0/20, 217.199.240.0/21, 217.199.248.0/22, 217.199.252.0/23, 217.199.254.0/24 diff --git a/ios/WireGuardGo/iphoneos/libwg-go.a b/ios/WireGuardGo/iphoneos/libwg-go.a deleted file mode 100644 index 2fef8a5..0000000 Binary files a/ios/WireGuardGo/iphoneos/libwg-go.a and /dev/null differ diff --git a/ios/WireGuardGo/iphonesimulator/libwg-go.a b/ios/WireGuardGo/iphonesimulator/libwg-go.a deleted file mode 100644 index 268f2d2..0000000 Binary files a/ios/WireGuardGo/iphonesimulator/libwg-go.a and /dev/null differ diff --git a/ios/WireGuardKit/COPYING b/ios/WireGuardKit/COPYING deleted file mode 100644 index af11a81..0000000 --- a/ios/WireGuardKit/COPYING +++ /dev/null @@ -1,19 +0,0 @@ -Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/ios/WireGuardKit/Package.swift b/ios/WireGuardKit/Package.swift deleted file mode 100644 index 4d4e07d..0000000 --- a/ios/WireGuardKit/Package.swift +++ /dev/null @@ -1,15 +0,0 @@ -// swift-tools-version:5.5 -// Vendored WireGuardKit (github.com/WireGuard/wireguard-apple, MIT). libwg-go.a is -// prebuilt in ../WireGuardGo// — the app target adds it to LIBRARY_SEARCH_PATHS. -import PackageDescription - -let package = Package( - name: "WireGuardKit", - platforms: [.iOS(.v15)], - products: [.library(name: "WireGuardKit", targets: ["WireGuardKit"])], - targets: [ - .target(name: "WireGuardKit", dependencies: ["WireGuardKitGo", "WireGuardKitC"]), - .target(name: "WireGuardKitC", publicHeadersPath: "."), - .target(name: "WireGuardKitGo", publicHeadersPath: ".", linkerSettings: [.linkedLibrary("wg-go")]), - ] -) diff --git a/ios/WireGuardKit/Sources/WireGuardKit/Array+ConcurrentMap.swift b/ios/WireGuardKit/Sources/WireGuardKit/Array+ConcurrentMap.swift deleted file mode 100644 index 13309c6..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/Array+ConcurrentMap.swift +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation - -extension Array { - - /// Returns an array containing the results of mapping the given closure over the sequence’s - /// elements concurrently. - /// - /// - Parameters: - /// - queue: The queue for performing concurrent computations. - /// If the given queue is serial, the values are mapped in a serial fashion. - /// Pass `nil` to perform computations on the current queue. - /// - transform: the block to perform concurrent computations over the given element. - /// - Returns: an array of concurrently computed values. - func concurrentMap(queue: DispatchQueue?, _ transform: (Element) -> U) -> [U] { - var result = [U?](repeating: nil, count: self.count) - let resultQueue = DispatchQueue(label: "ConcurrentMapQueue") - - let execute = queue?.sync ?? { $0() } - - execute { - DispatchQueue.concurrentPerform(iterations: self.count) { index in - let value = transform(self[index]) - resultQueue.sync { - result[index] = value - } - } - } - - return result.map { $0! } - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift b/ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift deleted file mode 100644 index cf4fde6..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/DNSResolver.swift +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Network -import Foundation - -enum DNSResolver {} - -extension DNSResolver { - - /// Concurrent queue used for DNS resolutions - private static let resolverQueue = DispatchQueue(label: "DNSResolverQueue", qos: .default, attributes: .concurrent) - - static func resolveSync(endpoints: [Endpoint?]) -> [Result?] { - let isAllEndpointsAlreadyResolved = endpoints.allSatisfy { maybeEndpoint -> Bool in - return maybeEndpoint?.hasHostAsIPAddress() ?? true - } - - if isAllEndpointsAlreadyResolved { - return endpoints.map { endpoint in - return endpoint.map { .success($0) } - } - } - - return endpoints.concurrentMap(queue: resolverQueue) { endpoint -> Result? in - guard let endpoint = endpoint else { return nil } - - if endpoint.hasHostAsIPAddress() { - return .success(endpoint) - } else { - return Result { try DNSResolver.resolveSync(endpoint: endpoint) } - .mapError { error -> DNSResolutionError in - // swiftlint:disable:next force_cast - return error as! DNSResolutionError - } - } - } - } - - private static func resolveSync(endpoint: Endpoint) throws -> Endpoint { - guard case .name(let name, _) = endpoint.host else { - return endpoint - } - - var hints = addrinfo() - hints.ai_flags = AI_ALL // We set this to ALL so that we get v4 addresses even on DNS64 networks - hints.ai_family = AF_UNSPEC - hints.ai_socktype = SOCK_DGRAM - hints.ai_protocol = IPPROTO_UDP - - var resultPointer: UnsafeMutablePointer? - defer { - resultPointer.flatMap { freeaddrinfo($0) } - } - - let errorCode = getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer) - if errorCode != 0 { - throw DNSResolutionError(errorCode: errorCode, address: name) - } - - var ipv4Address: IPv4Address? - var ipv6Address: IPv6Address? - - var next: UnsafeMutablePointer? = resultPointer - let iterator = AnyIterator { () -> addrinfo? in - let result = next?.pointee - next = result?.ai_next - return result - } - - for addrInfo in iterator { - if let maybeIpv4Address = IPv4Address(addrInfo: addrInfo) { - ipv4Address = maybeIpv4Address - break // If we found an IPv4 address, we can stop - } else if let maybeIpv6Address = IPv6Address(addrInfo: addrInfo) { - ipv6Address = maybeIpv6Address - continue // If we already have an IPv6 address, we can skip this one - } - } - - // We prefer an IPv4 address over an IPv6 address - if let ipv4Address = ipv4Address { - return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port) - } else if let ipv6Address = ipv6Address { - return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port) - } else { - // Must never happen - fatalError() - } - } -} - -extension Endpoint { - func withReresolvedIP() throws -> Endpoint { - #if os(iOS) - let hostname: String - switch host { - case .name(let name, _): - hostname = name - case .ipv4(let address): - hostname = "\(address)" - case .ipv6(let address): - hostname = "\(address)" - @unknown default: - fatalError() - } - - var hints = addrinfo() - hints.ai_family = AF_UNSPEC - hints.ai_socktype = SOCK_DGRAM - hints.ai_protocol = IPPROTO_UDP - hints.ai_flags = 0 // We set this to zero so that we actually resolve this using DNS64 - - var result: UnsafeMutablePointer? - defer { - result.flatMap { freeaddrinfo($0) } - } - - let errorCode = getaddrinfo(hostname, "\(self.port)", &hints, &result) - if errorCode != 0 { - throw DNSResolutionError(errorCode: errorCode, address: hostname) - } - - let addrInfo = result!.pointee - if let ipv4Address = IPv4Address(addrInfo: addrInfo) { - return Endpoint(host: .ipv4(ipv4Address), port: port) - } else if let ipv6Address = IPv6Address(addrInfo: addrInfo) { - return Endpoint(host: .ipv6(ipv6Address), port: port) - } else { - fatalError() - } - #elseif os(macOS) - return self - #else - #error("Unimplemented") - #endif - } -} - -/// An error type describing DNS resolution error -public struct DNSResolutionError: LocalizedError { - public let errorCode: Int32 - public let address: String - - init(errorCode: Int32, address: String) { - self.errorCode = errorCode - self.address = address - } - - public var errorDescription: String? { - return String(cString: gai_strerror(errorCode)) - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift b/ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift deleted file mode 100644 index eaf10f8..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/DNSServer.swift +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import Network - -public struct DNSServer { - public let address: IPAddress - - public init(address: IPAddress) { - self.address = address - } -} - -extension DNSServer: Equatable { - public static func == (lhs: DNSServer, rhs: DNSServer) -> Bool { - return lhs.address.rawValue == rhs.address.rawValue - } -} - -extension DNSServer { - public var stringRepresentation: String { - return "\(address)" - } - - public init?(from addressString: String) { - if let addr = IPv4Address(addressString) { - address = addr - } else if let addr = IPv6Address(addressString) { - address = addr - } else { - return nil - } - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift b/ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift deleted file mode 100644 index da49088..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/Endpoint.swift +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import Network - -public struct Endpoint { - public let host: NWEndpoint.Host - public let port: NWEndpoint.Port - - public init(host: NWEndpoint.Host, port: NWEndpoint.Port) { - self.host = host - self.port = port - } -} - -extension Endpoint: Equatable { - public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool { - return lhs.host == rhs.host && lhs.port == rhs.port - } -} - -extension Endpoint: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(host) - hasher.combine(port) - } -} - -extension Endpoint { - public var stringRepresentation: String { - switch host { - case .name(let hostname, _): - return "\(hostname):\(port)" - case .ipv4(let address): - return "\(address):\(port)" - case .ipv6(let address): - return "[\(address)]:\(port)" - @unknown default: - fatalError() - } - } - - public init?(from string: String) { - // Separation of host and port is based on 'parse_endpoint' function in - // https://git.zx2c4.com/wireguard-tools/tree/src/config.c - guard !string.isEmpty else { return nil } - let startOfPort: String.Index - let hostString: String - if string.first! == "[" { - // Look for IPv6-style endpoint, like [::1]:80 - let startOfHost = string.index(after: string.startIndex) - guard let endOfHost = string.dropFirst().firstIndex(of: "]") else { return nil } - let afterEndOfHost = string.index(after: endOfHost) - if afterEndOfHost == string.endIndex { return nil } - guard string[afterEndOfHost] == ":" else { return nil } - startOfPort = string.index(after: afterEndOfHost) - hostString = String(string[startOfHost ..< endOfHost]) - } else { - // Look for an IPv4-style endpoint, like 127.0.0.1:80 - guard let endOfHost = string.firstIndex(of: ":") else { return nil } - startOfPort = string.index(after: endOfHost) - hostString = String(string[string.startIndex ..< endOfHost]) - } - guard let endpointPort = NWEndpoint.Port(String(string[startOfPort ..< string.endIndex])) else { return nil } - let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in - return !CharacterSet.urlHostAllowed.contains(char) - } - guard invalidCharacterIndex == nil else { return nil } - host = NWEndpoint.Host(hostString) - port = endpointPort - } -} - -extension Endpoint { - public func hasHostAsIPAddress() -> Bool { - switch host { - case .name: - return false - case .ipv4: - return true - case .ipv6: - return true - @unknown default: - fatalError() - } - } - - public func hostname() -> String? { - switch host { - case .name(let hostname, _): - return hostname - case .ipv4: - return nil - case .ipv6: - return nil - @unknown default: - fatalError() - } - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/IPAddress+AddrInfo.swift b/ios/WireGuardKit/Sources/WireGuardKit/IPAddress+AddrInfo.swift deleted file mode 100644 index 7fe1fa7..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/IPAddress+AddrInfo.swift +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import Network - -extension IPv4Address { - init?(addrInfo: addrinfo) { - guard addrInfo.ai_family == AF_INET else { return nil } - - let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { ptr -> Data in - return Data(bytes: &ptr.pointee.sin_addr, count: MemoryLayout.size) - } - - self.init(addressData) - } -} - -extension IPv6Address { - init?(addrInfo: addrinfo) { - guard addrInfo.ai_family == AF_INET6 else { return nil } - - let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { ptr -> Data in - return Data(bytes: &ptr.pointee.sin6_addr, count: MemoryLayout.size) - } - - self.init(addressData) - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift b/ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift deleted file mode 100644 index 60430af..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/IPAddressRange.swift +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import Network - -public struct IPAddressRange { - public let address: IPAddress - public let networkPrefixLength: UInt8 - - init(address: IPAddress, networkPrefixLength: UInt8) { - self.address = address - self.networkPrefixLength = networkPrefixLength - } -} - -extension IPAddressRange: Equatable { - public static func == (lhs: IPAddressRange, rhs: IPAddressRange) -> Bool { - return lhs.address.rawValue == rhs.address.rawValue && lhs.networkPrefixLength == rhs.networkPrefixLength - } -} - -extension IPAddressRange: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(address.rawValue) - hasher.combine(networkPrefixLength) - } -} - -extension IPAddressRange { - public var stringRepresentation: String { - return "\(address)/\(networkPrefixLength)" - } - - public init?(from string: String) { - guard let parsed = IPAddressRange.parseAddressString(string) else { return nil } - address = parsed.0 - networkPrefixLength = parsed.1 - } - - private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? { - let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex - let addressString = String(string[string.startIndex ..< endOfIPAddress]) - let address: IPAddress - if let addr = IPv4Address(addressString) { - address = addr - } else if let addr = IPv6Address(addressString) { - address = addr - } else { - return nil - } - - let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128 - var networkPrefixLength: UInt8 - if endOfIPAddress < string.endIndex { // "/" was located - let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress) - guard indexOfNetworkPrefixLength < string.endIndex else { return nil } - let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex] - guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil } - networkPrefixLength = min(npl, maxNetworkPrefixLength) - } else { - networkPrefixLength = maxNetworkPrefixLength - } - - return (address, networkPrefixLength) - } - - public func subnetMask() -> IPAddress { - if address is IPv4Address { - let mask = networkPrefixLength > 0 ? ~UInt32(0) << (32 - networkPrefixLength) : UInt32(0) - let bytes = Data([ - UInt8(truncatingIfNeeded: mask >> 24), - UInt8(truncatingIfNeeded: mask >> 16), - UInt8(truncatingIfNeeded: mask >> 8), - UInt8(truncatingIfNeeded: mask >> 0) - ]) - return IPv4Address(bytes)! - } - if address is IPv6Address { - var bytes = Data(repeating: 0, count: 16) - for i in 0..> 24) - bytes[i + 1] = UInt8(truncatingIfNeeded: mask >> 16) - bytes[i + 2] = UInt8(truncatingIfNeeded: mask >> 8) - bytes[i + 3] = UInt8(truncatingIfNeeded: mask >> 0) - } - return IPv6Address(bytes)! - } - fatalError() - } - - public func maskedAddress() -> IPAddress { - let subnet = subnetMask().rawValue - var masked = Data(address.rawValue) - if subnet.count != masked.count { - fatalError() - } - for i in 0.. Bool { - let lhsAddresses = lhs.addresses.filter { $0.address is IPv4Address } + lhs.addresses.filter { $0.address is IPv6Address } - let rhsAddresses = rhs.addresses.filter { $0.address is IPv4Address } + rhs.addresses.filter { $0.address is IPv6Address } - - return lhs.privateKey == rhs.privateKey && - lhsAddresses == rhsAddresses && - lhs.listenPort == rhs.listenPort && - lhs.mtu == rhs.mtu && - lhs.dns == rhs.dns && - lhs.dnsSearch == rhs.dnsSearch - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift b/ios/WireGuardKit/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift deleted file mode 100644 index 1e1acea..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import Network -import NetworkExtension - -#if SWIFT_PACKAGE -import WireGuardKitC -#endif - -/// A type alias for `Result` type that holds a tuple with source and resolved endpoint. -typealias EndpointResolutionResult = Result<(Endpoint, Endpoint), DNSResolutionError> - -/// PCA Phobos: IPv4 CIDRs that must not go into the tunnel (set before adapter.start). -public enum PhobosRoutes { - public static var excludedIPv4: [String] = [] -} - -class PacketTunnelSettingsGenerator { - let tunnelConfiguration: TunnelConfiguration - let resolvedEndpoints: [Endpoint?] - - init(tunnelConfiguration: TunnelConfiguration, resolvedEndpoints: [Endpoint?]) { - self.tunnelConfiguration = tunnelConfiguration - self.resolvedEndpoints = resolvedEndpoints - } - - func endpointUapiConfiguration() -> (String, [EndpointResolutionResult?]) { - var resolutionResults = [EndpointResolutionResult?]() - var wgSettings = "" - - assert(tunnelConfiguration.peers.count == resolvedEndpoints.count) - for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) { - wgSettings.append("public_key=\(peer.publicKey.hexKey)\n") - - let result = resolvedEndpoint.map(Self.reresolveEndpoint) - if case .success((_, let resolvedEndpoint)) = result { - if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") } - wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n") - } - resolutionResults.append(result) - } - - return (wgSettings, resolutionResults) - } - - func uapiConfiguration() -> (String, [EndpointResolutionResult?]) { - var resolutionResults = [EndpointResolutionResult?]() - var wgSettings = "" - wgSettings.append("private_key=\(tunnelConfiguration.interface.privateKey.hexKey)\n") - if let listenPort = tunnelConfiguration.interface.listenPort { - wgSettings.append("listen_port=\(listenPort)\n") - } - if !tunnelConfiguration.peers.isEmpty { - wgSettings.append("replace_peers=true\n") - } - assert(tunnelConfiguration.peers.count == resolvedEndpoints.count) - for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) { - wgSettings.append("public_key=\(peer.publicKey.hexKey)\n") - if let preSharedKey = peer.preSharedKey?.hexKey { - wgSettings.append("preshared_key=\(preSharedKey)\n") - } - - let result = resolvedEndpoint.map(Self.reresolveEndpoint) - if case .success((_, let resolvedEndpoint)) = result { - if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") } - wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n") - } - resolutionResults.append(result) - - let persistentKeepAlive = peer.persistentKeepAlive ?? 0 - wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n") - if !peer.allowedIPs.isEmpty { - wgSettings.append("replace_allowed_ips=true\n") - peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation)\n") } - } - } - return (wgSettings, resolutionResults) - } - - func generateNetworkSettings() -> NEPacketTunnelNetworkSettings { - /* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for - * a tunnel to have no endpoint, or for there to be many endpoints, in - * which case, displaying a single one in settings doesn't really - * make sense. So, we fill it in with this placeholder, which is not - * a valid IP address that will actually route over the Internet. - */ - let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") - - if !tunnelConfiguration.interface.dnsSearch.isEmpty || !tunnelConfiguration.interface.dns.isEmpty { - let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation } - let dnsSettings = NEDNSSettings(servers: dnsServerStrings) - dnsSettings.searchDomains = tunnelConfiguration.interface.dnsSearch - if !tunnelConfiguration.interface.dns.isEmpty { - dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS - } - networkSettings.dnsSettings = dnsSettings - } - - let mtu = tunnelConfiguration.interface.mtu ?? 0 - - /* 0 means automatic MTU. In theory, we should just do - * `networkSettings.tunnelOverheadBytes = 80` but in - * practice there are too many broken networks out there. - * Instead set it to 1280. Boohoo. Maybe someday we'll - * add a nob, maybe, or iOS will do probing for us. - */ - if mtu == 0 { - #if os(iOS) - networkSettings.mtu = NSNumber(value: 1280) - #elseif os(macOS) - networkSettings.tunnelOverheadBytes = 80 - #else - #error("Unimplemented") - #endif - } else { - networkSettings.mtu = NSNumber(value: mtu) - } - - let (ipv4Addresses, ipv6Addresses) = addresses() - let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes() - - let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses.map { $0.destinationAddress }, subnetMasks: ipv4Addresses.map { $0.destinationSubnetMask }) - ipv4Settings.includedRoutes = ipv4IncludedRoutes - // PCA Phobos: "Russian sites direct" = these networks bypass the tunnel - ipv4Settings.excludedRoutes = PhobosRoutes.excludedIPv4.compactMap { cidr in - guard let range = IPAddressRange(from: cidr) else { return nil } - return NEIPv4Route(destinationAddress: "\(range.address)", subnetMask: "\(range.subnetMask())") - } - networkSettings.ipv4Settings = ipv4Settings - - let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses.map { $0.destinationAddress }, networkPrefixLengths: ipv6Addresses.map { $0.destinationNetworkPrefixLength }) - ipv6Settings.includedRoutes = ipv6IncludedRoutes - networkSettings.ipv6Settings = ipv6Settings - - return networkSettings - } - - private func addresses() -> ([NEIPv4Route], [NEIPv6Route]) { - var ipv4Routes = [NEIPv4Route]() - var ipv6Routes = [NEIPv6Route]() - for addressRange in tunnelConfiguration.interface.addresses { - if addressRange.address is IPv4Address { - ipv4Routes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())")) - } else if addressRange.address is IPv6Address { - /* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have - * any effect on iOS is a /120, so we clamp everything above to /120. This is potentially - * very bad, if various network parameters were actually relying on that subnet being - * intentionally small. TODO: talk about this with upstream iOS devs. - */ - ipv6Routes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: min(120, addressRange.networkPrefixLength)))) - } - } - return (ipv4Routes, ipv6Routes) - } - - private func includedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) { - var ipv4IncludedRoutes = [NEIPv4Route]() - var ipv6IncludedRoutes = [NEIPv6Route]() - - for addressRange in tunnelConfiguration.interface.addresses { - if addressRange.address is IPv4Address { - let route = NEIPv4Route(destinationAddress: "\(addressRange.maskedAddress())", subnetMask: "\(addressRange.subnetMask())") - route.gatewayAddress = "\(addressRange.address)" - ipv4IncludedRoutes.append(route) - } else if addressRange.address is IPv6Address { - let route = NEIPv6Route(destinationAddress: "\(addressRange.maskedAddress())", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength)) - route.gatewayAddress = "\(addressRange.address)" - ipv6IncludedRoutes.append(route) - } - } - - for peer in tunnelConfiguration.peers { - for addressRange in peer.allowedIPs { - if addressRange.address is IPv4Address { - ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())")) - } else if addressRange.address is IPv6Address { - ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength))) - } - } - } - return (ipv4IncludedRoutes, ipv6IncludedRoutes) - } - - private class func reresolveEndpoint(endpoint: Endpoint) -> EndpointResolutionResult { - return Result { (endpoint, try endpoint.withReresolvedIP()) } - .mapError { error -> DNSResolutionError in - // swiftlint:disable:next force_cast - return error as! DNSResolutionError - } - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/PeerConfiguration.swift b/ios/WireGuardKit/Sources/WireGuardKit/PeerConfiguration.swift deleted file mode 100644 index bc9276f..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/PeerConfiguration.swift +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation - -public struct PeerConfiguration { - public var publicKey: PublicKey - public var preSharedKey: PreSharedKey? - public var allowedIPs = [IPAddressRange]() - public var endpoint: Endpoint? - public var persistentKeepAlive: UInt16? - public var rxBytes: UInt64? - public var txBytes: UInt64? - public var lastHandshakeTime: Date? - - public init(publicKey: PublicKey) { - self.publicKey = publicKey - } -} - -extension PeerConfiguration: Equatable { - public static func == (lhs: PeerConfiguration, rhs: PeerConfiguration) -> Bool { - return lhs.publicKey == rhs.publicKey && - lhs.preSharedKey == rhs.preSharedKey && - Set(lhs.allowedIPs) == Set(rhs.allowedIPs) && - lhs.endpoint == rhs.endpoint && - lhs.persistentKeepAlive == rhs.persistentKeepAlive - } -} - -extension PeerConfiguration: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(publicKey) - hasher.combine(preSharedKey) - hasher.combine(Set(allowedIPs)) - hasher.combine(endpoint) - hasher.combine(persistentKeepAlive) - - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift b/ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift deleted file mode 100644 index 79d389d..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/PrivateKey.swift +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation - -#if SWIFT_PACKAGE -import WireGuardKitC -#endif - -/// The class describing a private key used by WireGuard. -public class PrivateKey: BaseKey { - /// Derived public key - public var publicKey: PublicKey { - return rawValue.withUnsafeBytes { (privateKeyBufferPointer: UnsafeRawBufferPointer) -> PublicKey in - var publicKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN)) - let privateKeyBytes = privateKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) - - publicKeyData.withUnsafeMutableBytes { (publicKeyBufferPointer: UnsafeMutableRawBufferPointer) in - let publicKeyBytes = publicKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) - curve25519_derive_public_key(publicKeyBytes, privateKeyBytes) - } - - return PublicKey(rawValue: publicKeyData)! - } - } - - /// Initialize new private key - convenience public init() { - var privateKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN)) - privateKeyData.withUnsafeMutableBytes { (rawBufferPointer: UnsafeMutableRawBufferPointer) in - let privateKeyBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) - curve25519_generate_private_key(privateKeyBytes) - } - self.init(rawValue: privateKeyData)! - } -} - -/// The class describing a public key used by WireGuard. -public class PublicKey: BaseKey {} - -/// The class describing a pre-shared key used by WireGuard. -public class PreSharedKey: BaseKey {} - -/// The base key implementation. Should not be used directly. -public class BaseKey: RawRepresentable, Equatable, Hashable { - /// Raw key representation - public let rawValue: Data - - /// Hex encoded representation - public var hexKey: String { - return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in - let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) - var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_HEX)) - key_to_hex(&outBytes, inBytes) - return String(cString: outBytes, encoding: .ascii)! - } - } - - /// Base64 encoded representation - public var base64Key: String { - return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in - let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) - var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_BASE64)) - key_to_base64(&outBytes, inBytes) - return String(cString: outBytes, encoding: .ascii)! - } - } - - /// Initialize the key with existing raw representation - required public init?(rawValue: Data) { - if rawValue.count == WG_KEY_LEN { - self.rawValue = rawValue - } else { - return nil - } - } - - /// Initialize the key with hex representation - public convenience init?(hexKey: String) { - var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN)) - let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in - return key_from_hex(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), hexKey) - } - if success { - self.init(rawValue: bytes) - } else { - return nil - } - } - - /// Initialize the key with base64 representation - public convenience init?(base64Key: String) { - var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN)) - let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in - return key_from_base64(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), base64Key) - } - if success { - self.init(rawValue: bytes) - } else { - return nil - } - } - - public static func == (lhs: BaseKey, rhs: BaseKey) -> Bool { - return lhs.rawValue.withUnsafeBytes { (lhsBytes: UnsafeRawBufferPointer) -> Bool in - return rhs.rawValue.withUnsafeBytes { (rhsBytes: UnsafeRawBufferPointer) -> Bool in - return key_eq( - lhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), - rhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) - ) - } - } - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/TunnelConfiguration.swift b/ios/WireGuardKit/Sources/WireGuardKit/TunnelConfiguration.swift deleted file mode 100644 index a59ba2f..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/TunnelConfiguration.swift +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation - -public final class TunnelConfiguration { - public var name: String? - public var interface: InterfaceConfiguration - public let peers: [PeerConfiguration] - - public init(name: String?, interface: InterfaceConfiguration, peers: [PeerConfiguration]) { - self.interface = interface - self.peers = peers - self.name = name - - let peerPublicKeysArray = peers.map { $0.publicKey } - let peerPublicKeysSet = Set(peerPublicKeysArray) - if peerPublicKeysArray.count != peerPublicKeysSet.count { - fatalError("Two or more peers cannot have the same public key") - } - } -} - -extension TunnelConfiguration: Equatable { - public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool { - return lhs.name == rhs.name && - lhs.interface == rhs.interface && - Set(lhs.peers) == Set(rhs.peers) - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift b/ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift deleted file mode 100644 index f7be19b..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKit/WireGuardAdapter.swift +++ /dev/null @@ -1,487 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -import Foundation -import NetworkExtension - -#if SWIFT_PACKAGE -import WireGuardKitGo -import WireGuardKitC -#endif - -public enum WireGuardAdapterError: Error { - /// Failure to locate tunnel file descriptor. - case cannotLocateTunnelFileDescriptor - - /// Failure to perform an operation in such state. - case invalidState - - /// Failure to resolve endpoints. - case dnsResolution([DNSResolutionError]) - - /// Failure to set network settings. - case setNetworkSettings(Error) - - /// Failure to start WireGuard backend. - case startWireGuardBackend(Int32) -} - -/// Enum representing internal state of the `WireGuardAdapter` -private enum State { - /// The tunnel is stopped - case stopped - - /// The tunnel is up and running - case started(_ handle: Int32, _ settingsGenerator: PacketTunnelSettingsGenerator) - - /// The tunnel is temporarily shutdown due to device going offline - case temporaryShutdown(_ settingsGenerator: PacketTunnelSettingsGenerator) -} - -public class WireGuardAdapter { - public typealias LogHandler = (WireGuardLogLevel, String) -> Void - - /// Network routes monitor. - private var networkMonitor: NWPathMonitor? - - /// Packet tunnel provider. - private weak var packetTunnelProvider: NEPacketTunnelProvider? - - /// Log handler closure. - private let logHandler: LogHandler - - /// Private queue used to synchronize access to `WireGuardAdapter` members. - private let workQueue = DispatchQueue(label: "WireGuardAdapterWorkQueue") - - /// Adapter state. - private var state: State = .stopped - - /// Tunnel device file descriptor. - private var tunnelFileDescriptor: Int32? { - var ctlInfo = ctl_info() - withUnsafeMutablePointer(to: &ctlInfo.ctl_name) { - $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) { - _ = strcpy($0, "com.apple.net.utun_control") - } - } - for fd: Int32 in 0...1024 { - var addr = sockaddr_ctl() - var ret: Int32 = -1 - var len = socklen_t(MemoryLayout.size(ofValue: addr)) - withUnsafeMutablePointer(to: &addr) { - $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { - ret = getpeername(fd, $0, &len) - } - } - if ret != 0 || addr.sc_family != AF_SYSTEM { - continue - } - if ctlInfo.ctl_id == 0 { - ret = ioctl(fd, CTLIOCGINFO, &ctlInfo) - if ret != 0 { - continue - } - } - if addr.sc_id == ctlInfo.ctl_id { - return fd - } - } - return nil - } - - /// Returns a WireGuard version. - class var backendVersion: String { - guard let ver = wgVersion() else { return "unknown" } - let str = String(cString: ver) - free(UnsafeMutableRawPointer(mutating: ver)) - return str - } - - /// Returns the tunnel device interface name, or nil on error. - /// - Returns: String. - public var interfaceName: String? { - guard let tunnelFileDescriptor = self.tunnelFileDescriptor else { return nil } - - var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ)) - - return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in - guard let baseAddress = mutableBufferPointer.baseAddress else { return nil } - - var ifnameSize = socklen_t(IFNAMSIZ) - let result = getsockopt( - tunnelFileDescriptor, - 2 /* SYSPROTO_CONTROL */, - 2 /* UTUN_OPT_IFNAME */, - baseAddress, - &ifnameSize) - - if result == 0 { - return String(cString: baseAddress) - } else { - return nil - } - } - } - - // MARK: - Initialization - - /// Designated initializer. - /// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored - /// as a weak reference. - /// - Parameter logHandler: a log handler closure. - public init(with packetTunnelProvider: NEPacketTunnelProvider, logHandler: @escaping LogHandler) { - self.packetTunnelProvider = packetTunnelProvider - self.logHandler = logHandler - - setupLogHandler() - } - - deinit { - // Force remove logger to make sure that no further calls to the instance of this class - // can happen after deallocation. - wgSetLogger(nil, nil) - - // Cancel network monitor - networkMonitor?.cancel() - - // Shutdown the tunnel - if case .started(let handle, _) = self.state { - wgTurnOff(handle) - } - } - - // MARK: - Public methods - - /// Returns a runtime configuration from WireGuard. - /// - Parameter completionHandler: completion handler. - public func getRuntimeConfiguration(completionHandler: @escaping (String?) -> Void) { - workQueue.async { - guard case .started(let handle, _) = self.state else { - completionHandler(nil) - return - } - - if let settings = wgGetConfig(handle) { - completionHandler(String(cString: settings)) - free(settings) - } else { - completionHandler(nil) - } - } - } - - /// Start the tunnel tunnel. - /// - Parameters: - /// - tunnelConfiguration: tunnel configuration. - /// - completionHandler: completion handler. - public func start(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) { - workQueue.async { - guard case .stopped = self.state else { - completionHandler(.invalidState) - return - } - - let networkMonitor = NWPathMonitor() - networkMonitor.pathUpdateHandler = { [weak self] path in - self?.didReceivePathUpdate(path: path) - } - networkMonitor.start(queue: self.workQueue) - - do { - let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration) - try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) - - let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() - self.logEndpointResolutionResults(resolutionResults) - - self.state = .started( - try self.startWireGuardBackend(wgConfig: wgConfig), - settingsGenerator - ) - self.networkMonitor = networkMonitor - completionHandler(nil) - } catch let error as WireGuardAdapterError { - networkMonitor.cancel() - completionHandler(error) - } catch { - fatalError() - } - } - } - - /// Stop the tunnel. - /// - Parameter completionHandler: completion handler. - public func stop(completionHandler: @escaping (WireGuardAdapterError?) -> Void) { - workQueue.async { - switch self.state { - case .started(let handle, _): - wgTurnOff(handle) - - case .temporaryShutdown: - break - - case .stopped: - completionHandler(.invalidState) - return - } - - self.networkMonitor?.cancel() - self.networkMonitor = nil - - self.state = .stopped - - completionHandler(nil) - } - } - - /// Update runtime configuration. - /// - Parameters: - /// - tunnelConfiguration: tunnel configuration. - /// - completionHandler: completion handler. - public func update(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) { - workQueue.async { - if case .stopped = self.state { - completionHandler(.invalidState) - return - } - - // Tell the system that the tunnel is going to reconnect using new WireGuard - // configuration. - // This will broadcast the `NEVPNStatusDidChange` notification to the GUI process. - self.packetTunnelProvider?.reasserting = true - defer { - self.packetTunnelProvider?.reasserting = false - } - - do { - let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration) - try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) - - switch self.state { - case .started(let handle, _): - let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() - self.logEndpointResolutionResults(resolutionResults) - - wgSetConfig(handle, wgConfig) - #if os(iOS) - wgDisableSomeRoamingForBrokenMobileSemantics(handle) - #endif - - self.state = .started(handle, settingsGenerator) - - case .temporaryShutdown: - self.state = .temporaryShutdown(settingsGenerator) - - case .stopped: - fatalError() - } - - completionHandler(nil) - } catch let error as WireGuardAdapterError { - completionHandler(error) - } catch { - fatalError() - } - } - } - - // MARK: - Private methods - - /// Setup WireGuard log handler. - private func setupLogHandler() { - let context = Unmanaged.passUnretained(self).toOpaque() - wgSetLogger(context) { context, logLevel, message in - guard let context = context, let message = message else { return } - - let unretainedSelf = Unmanaged.fromOpaque(context) - .takeUnretainedValue() - - let swiftString = String(cString: message).trimmingCharacters(in: .newlines) - let tunnelLogLevel = WireGuardLogLevel(rawValue: logLevel) ?? .verbose - - unretainedSelf.logHandler(tunnelLogLevel, swiftString) - } - } - - /// Set network tunnel configuration. - /// This method ensures that the call to `setTunnelNetworkSettings` does not time out, as in - /// certain scenarios the completion handler given to it may not be invoked by the system. - /// - /// - Parameters: - /// - networkSettings: an instance of type `NEPacketTunnelNetworkSettings`. - /// - Throws: an error of type `WireGuardAdapterError`. - /// - Returns: `PacketTunnelSettingsGenerator`. - private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings) throws { - var systemError: Error? - let condition = NSCondition() - - // Activate the condition - condition.lock() - defer { condition.unlock() } - - self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in - systemError = error - condition.signal() - } - - // Packet tunnel's `setTunnelNetworkSettings` times out in certain - // scenarios & never calls the given callback. - let setTunnelNetworkSettingsTimeout: TimeInterval = 5 // seconds - - if condition.wait(until: Date().addingTimeInterval(setTunnelNetworkSettingsTimeout)) { - if let systemError = systemError { - throw WireGuardAdapterError.setNetworkSettings(systemError) - } - } else { - self.logHandler(.error, "setTunnelNetworkSettings timed out after 5 seconds; proceeding anyway") - } - } - - /// Resolve peers of the given tunnel configuration. - /// - Parameter tunnelConfiguration: tunnel configuration. - /// - Throws: an error of type `WireGuardAdapterError`. - /// - Returns: The list of resolved endpoints. - private func resolvePeers(for tunnelConfiguration: TunnelConfiguration) throws -> [Endpoint?] { - let endpoints = tunnelConfiguration.peers.map { $0.endpoint } - let resolutionResults = DNSResolver.resolveSync(endpoints: endpoints) - let resolutionErrors = resolutionResults.compactMap { result -> DNSResolutionError? in - if case .failure(let error) = result { - return error - } else { - return nil - } - } - assert(endpoints.count == resolutionResults.count) - guard resolutionErrors.isEmpty else { - throw WireGuardAdapterError.dnsResolution(resolutionErrors) - } - - let resolvedEndpoints = resolutionResults.map { result -> Endpoint? in - // swiftlint:disable:next force_try - return try! result?.get() - } - - return resolvedEndpoints - } - - /// Start WireGuard backend. - /// - Parameter wgConfig: WireGuard configuration - /// - Throws: an error of type `WireGuardAdapterError` - /// - Returns: tunnel handle - private func startWireGuardBackend(wgConfig: String) throws -> Int32 { - guard let tunnelFileDescriptor = self.tunnelFileDescriptor else { - throw WireGuardAdapterError.cannotLocateTunnelFileDescriptor - } - - let handle = wgTurnOn(wgConfig, tunnelFileDescriptor) - if handle < 0 { - throw WireGuardAdapterError.startWireGuardBackend(handle) - } - #if os(iOS) - wgDisableSomeRoamingForBrokenMobileSemantics(handle) - #endif - return handle - } - - /// Resolves the hostnames in the given tunnel configuration and return settings generator. - /// - Parameter tunnelConfiguration: an instance of type `TunnelConfiguration`. - /// - Throws: an error of type `WireGuardAdapterError`. - /// - Returns: an instance of type `PacketTunnelSettingsGenerator`. - private func makeSettingsGenerator(with tunnelConfiguration: TunnelConfiguration) throws -> PacketTunnelSettingsGenerator { - return PacketTunnelSettingsGenerator( - tunnelConfiguration: tunnelConfiguration, - resolvedEndpoints: try self.resolvePeers(for: tunnelConfiguration) - ) - } - - /// Log DNS resolution results. - /// - Parameter resolutionErrors: an array of type `[DNSResolutionError]`. - private func logEndpointResolutionResults(_ resolutionResults: [EndpointResolutionResult?]) { - for case .some(let result) in resolutionResults { - switch result { - case .success((let sourceEndpoint, let resolvedEndpoint)): - if sourceEndpoint.host == resolvedEndpoint.host { - self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to itself.") - } else { - self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to \(resolvedEndpoint.host)") - } - case .failure(let resolutionError): - self.logHandler(.error, "Failed to resolve endpoint \(resolutionError.address): \(resolutionError.errorDescription ?? "(nil)")") - } - } - } - - /// Helper method used by network path monitor. - /// - Parameter path: new network path - private func didReceivePathUpdate(path: Network.NWPath) { - self.logHandler(.verbose, "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)") - - #if os(macOS) - if case .started(let handle, _) = self.state { - wgBumpSockets(handle) - } - #elseif os(iOS) - switch self.state { - case .started(let handle, let settingsGenerator): - if path.status.isSatisfiable { - let (wgConfig, resolutionResults) = settingsGenerator.endpointUapiConfiguration() - self.logEndpointResolutionResults(resolutionResults) - - wgSetConfig(handle, wgConfig) - wgDisableSomeRoamingForBrokenMobileSemantics(handle) - wgBumpSockets(handle) - } else { - self.logHandler(.verbose, "Connectivity offline, pausing backend.") - - self.state = .temporaryShutdown(settingsGenerator) - wgTurnOff(handle) - } - - case .temporaryShutdown(let settingsGenerator): - guard path.status.isSatisfiable else { return } - - self.logHandler(.verbose, "Connectivity online, resuming backend.") - - do { - try self.setNetworkSettings(settingsGenerator.generateNetworkSettings()) - - let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration() - self.logEndpointResolutionResults(resolutionResults) - - self.state = .started( - try self.startWireGuardBackend(wgConfig: wgConfig), - settingsGenerator - ) - } catch { - self.logHandler(.error, "Failed to restart backend: \(error.localizedDescription)") - } - - case .stopped: - // no-op - break - } - #else - #error("Unsupported") - #endif - } -} - -/// A enum describing WireGuard log levels defined in `api-apple.go`. -public enum WireGuardLogLevel: Int32 { - case verbose = 0 - case error = 1 -} - -private extension Network.NWPath.Status { - /// Returns `true` if the path is potentially satisfiable. - var isSatisfiable: Bool { - switch self { - case .requiresConnection, .satisfied: - return true - case .unsatisfied: - return false - @unknown default: - return true - } - } -} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h b/ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h deleted file mode 100644 index 1b8974d..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitC/WireGuardKitC.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. - -#include -#include "key.h" -#include "x25519.h" - -/* From */ -#define CTLIOCGINFO 0xc0644e03UL -struct ctl_info { - u_int32_t ctl_id; - char ctl_name[96]; -}; -struct sockaddr_ctl { - u_char sc_len; - u_char sc_family; - u_int16_t ss_sysaddr; - u_int32_t sc_id; - u_int32_t sc_unit; - u_int32_t sc_reserved[5]; -}; diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/key.c b/ios/WireGuardKit/Sources/WireGuardKitC/key.c deleted file mode 100644 index 84e7f16..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitC/key.c +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT -/* - * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. - * - * This is a specialized constant-time base64/hex implementation that resists side-channel attacks. - */ - -#include -#include "key.h" - -static inline void encode_base64(char dest[static 4], const uint8_t src[static 3]) -{ - const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 }; - - for (unsigned int i = 0; i < 4; ++i) - dest[i] = input[i] + 'A' - + (((25 - input[i]) >> 8) & 6) - - (((51 - input[i]) >> 8) & 75) - - (((61 - input[i]) >> 8) & 15) - + (((62 - input[i]) >> 8) & 3); - -} - -void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]) -{ - unsigned int i; - - for (i = 0; i < WG_KEY_LEN / 3; ++i) - encode_base64(&base64[i * 4], &key[i * 3]); - encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 }); - base64[WG_KEY_LEN_BASE64 - 2] = '='; - base64[WG_KEY_LEN_BASE64 - 1] = '\0'; -} - -static inline int decode_base64(const char src[static 4]) -{ - int val = 0; - - for (unsigned int i = 0; i < 4; ++i) - val |= (-1 - + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64)) - + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70)) - + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) - + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) - + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64) - ) << (18 - 6 * i); - return val; -} - -bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64) -{ - unsigned int i; - volatile uint8_t ret = 0; - int val; - - if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=') - return false; - - for (i = 0; i < WG_KEY_LEN / 3; ++i) { - val = decode_base64(&base64[i * 4]); - ret |= (uint32_t)val >> 31; - key[i * 3 + 0] = (val >> 16) & 0xff; - key[i * 3 + 1] = (val >> 8) & 0xff; - key[i * 3 + 2] = val & 0xff; - } - val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' }); - ret |= ((uint32_t)val >> 31) | (val & 0xff); - key[i * 3 + 0] = (val >> 16) & 0xff; - key[i * 3 + 1] = (val >> 8) & 0xff; - - return 1 & ((ret - 1) >> 8); -} - -void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]) -{ - unsigned int i; - - for (i = 0; i < WG_KEY_LEN; ++i) { - hex[i * 2] = 87U + (key[i] >> 4) + ((((key[i] >> 4) - 10U) >> 8) & ~38U); - hex[i * 2 + 1] = 87U + (key[i] & 0xf) + ((((key[i] & 0xf) - 10U) >> 8) & ~38U); - } - hex[i * 2] = '\0'; -} - -bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex) -{ - uint8_t c, c_acc, c_alpha0, c_alpha, c_num0, c_num, c_val; - volatile uint8_t ret = 0; - - if (strlen(hex) != WG_KEY_LEN_HEX - 1) - return false; - - for (unsigned int i = 0; i < WG_KEY_LEN_HEX - 1; i += 2) { - c = (uint8_t)hex[i]; - c_num = c ^ 48U; - c_num0 = (c_num - 10U) >> 8; - c_alpha = (c & ~32U) - 55U; - c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; - ret |= ((c_num0 | c_alpha0) - 1) >> 8; - c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); - c_acc = c_val * 16U; - - c = (uint8_t)hex[i + 1]; - c_num = c ^ 48U; - c_num0 = (c_num - 10U) >> 8; - c_alpha = (c & ~32U) - 55U; - c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; - ret |= ((c_num0 | c_alpha0) - 1) >> 8; - c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); - key[i / 2] = c_acc | c_val; - } - - return 1 & ((ret - 1) >> 8); -} - -bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN]) -{ - volatile uint8_t acc = 0; - for (unsigned int i = 0; i < WG_KEY_LEN; ++i) { - acc |= key1[i] ^ key2[i]; - asm volatile("" : "=r"(acc) : "0"(acc)); - } - return 1 & ((acc - 1) >> 8); -} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/key.h b/ios/WireGuardKit/Sources/WireGuardKitC/key.h deleted file mode 100644 index 5353ade..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitC/key.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. - */ - -#ifndef KEY_H -#define KEY_H - -#include -#include - -#define WG_KEY_LEN (32) -#define WG_KEY_LEN_BASE64 (45) -#define WG_KEY_LEN_HEX (65) - -void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]); -bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64); - -void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]); -bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex); - -bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN]); - -#endif diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap b/ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap deleted file mode 100644 index 26b45bf..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitC/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module WireGuardKitC { - umbrella header "WireGuardKitC.h" - export * -} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/x25519.c b/ios/WireGuardKit/Sources/WireGuardKitC/x25519.c deleted file mode 100644 index 7793299..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitC/x25519.c +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. - * - * Curve25519 ECDH functions, based on TweetNaCl but cleaned up. - */ - -#include -#include -#include -#include - -#include "x25519.h" - -typedef int64_t fe[16]; - -static inline void carry(fe o) -{ - int i; - - for (i = 0; i < 16; ++i) { - o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16); - o[i] &= 0xffff; - } -} - -static inline void cswap(fe p, fe q, int b) -{ - int i; - int64_t t, c = ~(b - 1); - - for (i = 0; i < 16; ++i) { - t = c & (p[i] ^ q[i]); - p[i] ^= t; - q[i] ^= t; - } -} - -static inline void pack(uint8_t *o, const fe n) -{ - int i, j, b; - fe m, t; - - memcpy(t, n, sizeof(t)); - carry(t); - carry(t); - carry(t); - for (j = 0; j < 2; ++j) { - m[0] = t[0] - 0xffed; - for (i = 1; i < 15; ++i) { - m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); - m[i - 1] &= 0xffff; - } - m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); - b = (m[15] >> 16) & 1; - m[14] &= 0xffff; - cswap(t, m, 1 - b); - } - for (i = 0; i < 16; ++i) { - o[2 * i] = t[i] & 0xff; - o[2 * i + 1] = t[i] >> 8; - } -} - -static inline void unpack(fe o, const uint8_t *n) -{ - int i; - - for (i = 0; i < 16; ++i) - o[i] = n[2 * i] + ((int64_t)n[2 * i + 1] << 8); - o[15] &= 0x7fff; -} - -static inline void add(fe o, const fe a, const fe b) -{ - int i; - - for (i = 0; i < 16; ++i) - o[i] = a[i] + b[i]; -} - -static inline void subtract(fe o, const fe a, const fe b) -{ - int i; - - for (i = 0; i < 16; ++i) - o[i] = a[i] - b[i]; -} - -static inline void multmod(fe o, const fe a, const fe b) -{ - int i, j; - int64_t t[31] = { 0 }; - - for (i = 0; i < 16; ++i) { - for (j = 0; j < 16; ++j) - t[i + j] += a[i] * b[j]; - } - for (i = 0; i < 15; ++i) - t[i] += 38 * t[i + 16]; - memcpy(o, t, sizeof(fe)); - carry(o); - carry(o); -} - -static inline void invert(fe o, const fe i) -{ - fe c; - int a; - - memcpy(c, i, sizeof(c)); - for (a = 253; a >= 0; --a) { - multmod(c, c, c); - if (a != 2 && a != 4) - multmod(c, c, i); - } - memcpy(o, c, sizeof(fe)); -} - -static void curve25519_shared_secret(uint8_t shared_secret[32], const uint8_t private_key[32], const uint8_t public_key[32]) -{ - static const fe a24 = { 0xdb41, 1 }; - uint8_t z[32]; - int64_t r; - int i; - fe a = { 1 }, b, c = { 0 }, d = { 1 }, e, f, x; - - memcpy(z, private_key, sizeof(z)); - - z[31] = (z[31] & 127) | 64; - z[0] &= 248; - - unpack(x, public_key); - memcpy(b, x, sizeof(b)); - - for (i = 254; i >= 0; --i) { - r = (z[i >> 3] >> (i & 7)) & 1; - cswap(a, b, (int)r); - cswap(c, d, (int)r); - add(e, a, c); - subtract(a, a, c); - add(c, b, d); - subtract(b, b, d); - multmod(d, e, e); - multmod(f, a, a); - multmod(a, c, a); - multmod(c, b, e); - add(e, a, c); - subtract(a, a, c); - multmod(b, a, a); - subtract(c, d, f); - multmod(a, c, a24); - add(a, a, d); - multmod(c, c, a); - multmod(a, d, f); - multmod(d, b, x); - multmod(b, e, e); - cswap(a, b, (int)r); - cswap(c, d, (int)r); - } - invert(c, c); - multmod(a, a, c); - pack(shared_secret, a); -} - -void curve25519_derive_public_key(uint8_t public_key[32], const uint8_t private_key[32]) -{ - static const uint8_t basepoint[32] = { 9 }; - - curve25519_shared_secret(public_key, private_key, basepoint); -} - -void curve25519_generate_private_key(uint8_t private_key[32]) -{ - assert(CCRandomGenerateBytes(private_key, 32) == kCCSuccess); - private_key[31] = (private_key[31] & 127) | 64; - private_key[0] &= 248; -} diff --git a/ios/WireGuardKit/Sources/WireGuardKitC/x25519.h b/ios/WireGuardKit/Sources/WireGuardKitC/x25519.h deleted file mode 100644 index 7d8440d..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitC/x25519.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef X25519_H -#define X25519_H - -void curve25519_derive_public_key(unsigned char public_key[32], const unsigned char private_key[32]); -void curve25519_generate_private_key(unsigned char private_key[32]); - -#endif diff --git a/ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c b/ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c deleted file mode 100644 index d15abba..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitGo/dummy.c +++ /dev/null @@ -1 +0,0 @@ -// Empty diff --git a/ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap b/ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap deleted file mode 100644 index 2ca3916..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitGo/module.modulemap +++ /dev/null @@ -1,5 +0,0 @@ -module WireGuardKitGo { - umbrella header "wireguard.h" - link "wg-go" - export * -} diff --git a/ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h b/ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h deleted file mode 100644 index fdb66c2..0000000 --- a/ios/WireGuardKit/Sources/WireGuardKitGo/wireguard.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2018-2023 WireGuard LLC. All Rights Reserved. - */ - -#ifndef WIREGUARD_H -#define WIREGUARD_H - -#include -#include -#include - -typedef void(*logger_fn_t)(void *context, int level, const char *msg); -extern void wgSetLogger(void *context, logger_fn_t logger_fn); -extern int wgTurnOn(const char *settings, int32_t tun_fd); -extern void wgTurnOff(int handle); -extern int64_t wgSetConfig(int handle, const char *settings); -extern char *wgGetConfig(int handle); -extern void wgBumpSockets(int handle); -extern void wgDisableSomeRoamingForBrokenMobileSemantics(int handle); -extern const char *wgVersion(); - -#endif diff --git a/ios/project.yml b/ios/project.yml deleted file mode 100644 index ca98508..0000000 --- a/ios/project.yml +++ /dev/null @@ -1,86 +0,0 @@ -# xcodegen spec: `xcodegen generate` -> PCAPhobos.xcodeproj -# Signing: set DEVELOPMENT_TEAM (paid Apple Developer account — Network Extension needs it). -name: PCAPhobos -options: - bundleIdPrefix: com.bobyrev - deploymentTarget: - iOS: "16.0" -packages: - WireGuardKit: - path: WireGuardKit -settings: - base: - MARKETING_VERSION: "1.0" - CURRENT_PROJECT_VERSION: "6" - DEVELOPMENT_TEAM: Q5FKU39TUA - CODE_SIGN_STYLE: Automatic -targets: - PCAPhobos: - type: application - platform: iOS - sources: - - App - - Shared - resources: - - App/Assets.xcassets - dependencies: - - target: PhobosTunnel - entitlements: - path: App/PCAPhobos.entitlements - properties: - com.apple.developer.networking.networkextension: [packet-tunnel-provider] - info: - path: App/Info.plist - properties: - CFBundleDisplayName: PCA Phobos - ITSAppUsesNonExemptEncryption: false - CFBundleShortVersionString: $(MARKETING_VERSION) - CFBundleVersion: $(CURRENT_PROJECT_VERSION) - UILaunchScreen: {} - UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait] - UIRequiresFullScreen: true - CFBundleURLTypes: - - CFBundleURLName: com.bobyrev.pcaphobos.link - CFBundleURLSchemes: [phobos] - settings: - base: - PRODUCT_BUNDLE_IDENTIFIER: com.bobyrev.pcaphobos - ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon - PhobosTunnel: - type: app-extension - platform: iOS - sources: - - Tunnel/PacketTunnelProvider.swift - - Tunnel/TunnelConfiguration+WgQuickConfig.swift - - Tunnel/String+ArrayConversion.swift - - Tunnel/pcarelay.c - - Tunnel/WsLink.swift - - Shared - - path: Tunnel/ru_bypass.txt - buildPhase: resources - - path: Tunnel/obfuscator - compilerFlags: ["-Dmain=wgobf_main", "-w"] - excludes: ["*.h"] - dependencies: - - package: WireGuardKit - entitlements: - path: Tunnel/PhobosTunnel.entitlements - properties: - com.apple.developer.networking.networkextension: [packet-tunnel-provider] - info: - path: Tunnel/Info.plist - properties: - CFBundleDisplayName: PhobosTunnel - CFBundleShortVersionString: $(MARKETING_VERSION) - CFBundleVersion: $(CURRENT_PROJECT_VERSION) - NSExtension: - NSExtensionPointIdentifier: com.apple.networkextension.packet-tunnel - NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).PacketTunnelProvider - settings: - base: - PRODUCT_BUNDLE_IDENTIFIER: com.bobyrev.pcaphobos.tunnel - SWIFT_OBJC_BRIDGING_HEADER: Tunnel/Tunnel-Bridging-Header.h - HEADER_SEARCH_PATHS: ["$(PROJECT_DIR)/Tunnel", "$(PROJECT_DIR)/Tunnel/obfuscator"] - "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]": ["$(PROJECT_DIR)/WireGuardGo/iphoneos"] - "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]": ["$(PROJECT_DIR)/WireGuardGo/iphonesimulator"] - "EXCLUDED_ARCHS[sdk=iphonesimulator*]": x86_64 diff --git a/overlay/install-obfuscator.sh b/overlay/install-obfuscator.sh index 6b8b480..7e9b4ca 100755 --- a/overlay/install-obfuscator.sh +++ b/overlay/install-obfuscator.sh @@ -23,6 +23,20 @@ resolve_install_names() { fi if [ -f "$binary_path" ] && [ -f "$PHOBOS_DIR/${CLIENT_NAME}.conf" ]; then + # 02.11.2026: переустановка — берём те же имена, что у этой учётки (по *.link), + # иначе доп. учётка затёрла бы обфускатор главной + local l n sc + for l in "$PHOBOS_DIR"/wg-obfuscator?*.link; do + [ "$(cat "$l" 2>/dev/null)" = "${CLIENT_NAME}" ] || continue + n=$(basename "$l" .link); n=${n#wg-obfuscator} + sc=$(grep -l "/wg-obfuscator${n}.conf\"\$" /opt/etc/init.d/S*wg-obfuscator 2>/dev/null | head -1) + OBF_BINARY_NAME="wg-obfuscator${n}" + OBF_CONF_NAME="wg-obfuscator${n}.conf" + OBF_INIT_NAME=$(basename "${sc:-S50wg-obfuscator}") + OBF_SERVICE_NAME="phobos-obfuscator${n}" + OBF_WG_IFACE="phobos${n}" + return + done return fi diff --git a/overlay/install-router.sh.template b/overlay/install-router.sh.template index b441eab..2014242 100644 --- a/overlay/install-router.sh.template +++ b/overlay/install-router.sh.template @@ -417,6 +417,16 @@ show_final_info() { } +# Убирает из crontab строки health/pull только этой учётки ($1 = "PHOBOS_ACC= " +# для доп. учётки, "PHOBOS_ACC=" для главной — у неё строки без PHOBOS_ACC). +drop_own_cron() { + if [ "$1" = "PHOBOS_ACC=" ]; then + awk '!/phobos-(health|pull)/ || /PHOBOS_ACC=/' + else + grep -v -F "$1" + fi +} + deploy_health_monitor() { log "Развертывание монитора здоровья Phobos..." @@ -428,12 +438,19 @@ deploy_health_monitor() { cp "phobos-health.sh" "$PHOBOS_DIR/phobos-health.sh" chmod +x "$PHOBOS_DIR/phobos-health.sh" - if [ -f "failover.conf" ]; then - cp "failover.conf" "$PHOBOS_DIR/failover.conf" - chmod 600 "$PHOBOS_DIR/failover.conf" + # 02.11.2026: вторая и следующие учётки на одном роутере — свой каталог + # (failover.conf, state, client_id, panel_*), свои строки cron с PHOBOS_ACC. + local A="$PHOBOS_DIR" envp="" mine="PHOBOS_ACC=" + local main_cid=$(cat "$A/client_id" 2>/dev/null) + if [ -n "$main_cid" ] && [ "$main_cid" != "{{CLIENT_NAME}}" ]; then + A="$PHOBOS_DIR/acc/{{CLIENT_NAME}}"; envp="PHOBOS_ACC=$A "; mine="PHOBOS_ACC=$A " + log " Дополнительная учётка: $A (главная — $main_cid)" + fi + mkdir -p "$A/state" + if [ -f "failover.conf" ]; then + cp "failover.conf" "$A/failover.conf" + chmod 600 "$A/failover.conf" fi - - mkdir -p "$PHOBOS_DIR/state" # --- Phobos pull agent: panel -> router config sync (NAT-friendly) --- # Routers behind NAT cannot be reached by the panel over SSH, so they PULL @@ -441,18 +458,18 @@ deploy_health_monitor() { if [ -f "phobos-pull.sh" ]; then cp "phobos-pull.sh" "$PHOBOS_DIR/phobos-pull.sh" chmod +x "$PHOBOS_DIR/phobos-pull.sh" - echo "{{CLIENT_NAME}}" > "$PHOBOS_DIR/client_id" - [ -f "pull_token" ] && cp "pull_token" "$PHOBOS_DIR/pull_token" - [ -f "panel_tunnel_ip" ] && cp "panel_tunnel_ip" "$PHOBOS_DIR/panel_tunnel_ip" - [ -f "panel_port" ] && cp "panel_port" "$PHOBOS_DIR/panel_port" - [ -f "panel_scheme" ] && cp "panel_scheme" "$PHOBOS_DIR/panel_scheme" - [ -f "panel_url" ] && cp "panel_url" "$PHOBOS_DIR/panel_url" - [ -f "panel_urls" ] && cp "panel_urls" "$PHOBOS_DIR/panel_urls" + echo "{{CLIENT_NAME}}" > "$A/client_id" + [ -f "pull_token" ] && cp "pull_token" "$A/pull_token" + [ -f "panel_tunnel_ip" ] && cp "panel_tunnel_ip" "$A/panel_tunnel_ip" + [ -f "panel_port" ] && cp "panel_port" "$A/panel_port" + [ -f "panel_scheme" ] && cp "panel_scheme" "$A/panel_scheme" + [ -f "panel_url" ] && cp "panel_url" "$A/panel_url" + [ -f "panel_urls" ] && cp "panel_urls" "$A/panel_urls" log " Pull-агент установлен (client_id={{CLIENT_NAME}})" fi - local cron_line="*/1 * * * * $PHOBOS_DIR/phobos-health.sh" - local pull_line="*/1 * * * * $PHOBOS_DIR/phobos-pull.sh" + local cron_line="*/1 * * * * ${envp}$PHOBOS_DIR/phobos-health.sh" + local pull_line="*/1 * * * * ${envp}$PHOBOS_DIR/phobos-pull.sh" [ -f "$PHOBOS_DIR/phobos-pull.sh" ] || pull_line="" if [ "$ROUTER_PLATFORM" = "keenetic" ]; then @@ -468,21 +485,21 @@ deploy_health_monitor() { mkdir -p /opt/var/spool/cron/crontabs # 01.11.2026: демоны cron на Keenetic (busybox crond, Entware cron) читают crontab # пользователя root, а не /opt/etc/crontab — туда писать бесполезно - sed -i "/phobos-health\|phobos-pull/d" /opt/etc/crontab 2>/dev/null || true - (crontab -l 2>/dev/null | grep -v "phobos-health" | grep -v "phobos-pull"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab - + [ -z "$envp" ] && sed -i "/phobos-health\|phobos-pull/d" /opt/etc/crontab 2>/dev/null || true + (crontab -l 2>/dev/null | drop_own_cron "$mine"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab - log " Cron добавлен через crontab (health + pull)" /opt/etc/init.d/S10cron restart >/dev/null 2>&1 || true elif [ "$ROUTER_PLATFORM" = "openwrt" ]; then - (crontab -l 2>/dev/null | grep -v "phobos-health" | grep -v "phobos-pull"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab - + (crontab -l 2>/dev/null | drop_own_cron "$mine"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab - log " Cron добавлен через crontab (health + pull)" elif [ "$ROUTER_PLATFORM" = "linux" ]; then - { echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line"; } > /etc/cron.d/phobos-health - chmod 644 /etc/cron.d/phobos-health + { echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line"; } > /etc/cron.d/phobos-health${envp:+-{{CLIENT_NAME}}} + chmod 644 /etc/cron.d/phobos-health* log " Cron добавлен в /etc/cron.d/phobos-health (health + pull)" fi log " Монитор здоровья: $PHOBOS_DIR/phobos-health.sh" - log " Failover конфиг: $PHOBOS_DIR/failover.conf" + log " Failover конфиг: $A/failover.conf" log " Проверка каждую минуту через cron" } diff --git a/overlay/phobos-pull.sh b/overlay/phobos-pull.sh index 26a51c8..014c3e7 100755 --- a/overlay/phobos-pull.sh +++ b/overlay/phobos-pull.sh @@ -1,4 +1,5 @@ #!/bin/sh +export PATH=/opt/bin:/opt/sbin:/opt/usr/bin:$PATH # cron без /opt/bin -> busybox wget висел вечно (09.29) # ============================================================ # Phobos Config Pull — NAT-friendly management channel. # Runs ON the router via cron (every 2 min). OUTBOUND ONLY: @@ -21,9 +22,11 @@ # PANEL env or default below -> panel base URL # ============================================================ PHOBOS_DIR="/opt/etc/Phobos" -CONF="$PHOBOS_DIR/failover.conf" +# 02.11.2026: доп. учётки — PHOBOS_ACC=$PHOBOS_DIR/acc/<клиент> (см. phobos-health.sh) +ACC_DIR="${PHOBOS_ACC:-$PHOBOS_DIR}" +CONF="$ACC_DIR/failover.conf" HEALTH="$PHOBOS_DIR/phobos-health.sh" -LOG="$PHOBOS_DIR/health.log" +LOG="$ACC_DIR/health.log" # Config sources, tried in order. TUNNEL FIRST: panel_tunnel_ip is the wg0 of # whatever server the tunnel currently terminates on, so management rides the # same obfuscated channel as data — survives a public-IP ban and follows @@ -31,15 +34,18 @@ LOG="$PHOBOS_DIR/health.log" # - panel_tunnel_ip:8444 -> secondary server agent (phobos-api) # - panel_tunnel_ip: -> primary server panel # - public panel IP -> bootstrap / if tunnel is down -PANEL_TUNNEL_IP="${PANEL_TUNNEL_IP:-$(cat "$PHOBOS_DIR/panel_tunnel_ip" 2>/dev/null)}" +PANEL_TUNNEL_IP="${PANEL_TUNNEL_IP:-$(cat "$ACC_DIR/panel_tunnel_ip" 2>/dev/null)}" [ -n "$PANEL_TUNNEL_IP" ] || PANEL_TUNNEL_IP="10.25.0.1" -PANEL="${PANEL:-$(cat "$PHOBOS_DIR/panel_url" 2>/dev/null)}" -PANEL_URLS="${PANEL_URLS:-$(cat "$PHOBOS_DIR/panel_urls" 2>/dev/null)}" +PANEL="${PANEL:-$(cat "$ACC_DIR/panel_url" 2>/dev/null)}" +PANEL_URLS="${PANEL_URLS:-$(cat "$ACC_DIR/panel_urls" 2>/dev/null)}" [ -n "$PANEL_URLS" ] || PANEL_URLS="http://${PANEL_TUNNEL_IP}:8444 $PANEL" -CLIENT_ID=$(cat "$PHOBOS_DIR/client_id" 2>/dev/null || echo "home") -TOKEN=$(cat "$PHOBOS_DIR/pull_token" 2>/dev/null) -TMP="/tmp/failover.conf.pull" -LOCK="/tmp/phobos-pull.lock" +# + public URLs the panel announced in failover.conf (second IP etc., 09.33) +_extra=$(grep "^PANEL_URLS_EXTRA=" "$CONF" 2>/dev/null | cut -d= -f2-) +for _u in $_extra; do case " $PANEL_URLS " in *" $_u "*) ;; *) PANEL_URLS="$PANEL_URLS $_u" ;; esac; done +CLIENT_ID=$(cat "$ACC_DIR/client_id" 2>/dev/null || echo "home") +TOKEN=$(cat "$ACC_DIR/pull_token" 2>/dev/null) +TMP="/tmp/failover.conf.pull${PHOBOS_ACC:+-$CLIENT_ID}" +LOCK="/tmp/phobos-pull${PHOBOS_ACC:+-$CLIENT_ID}.lock" log() { echo "$(date '+%H:%M:%S') $1" >> "$LOG"; } @@ -74,8 +80,21 @@ fetch_config() { wget -T 8 --no-check-certificate -q -O "$TMP" "$url" 2>/dev/null || wget -T 8 -q -O "$TMP" "$url" 2>/dev/null } +# TCP channel for routers (1.5.0): the guard needs /opt/bin/udptcp when failover.conf has TCP_N. +ensure_udptcp() { + [ -x /opt/bin/udptcp ] && return 0 + grep -q "^TCP_[0-9]*=" "$CONF" 2>/dev/null || return 0 + case "$(uname -m)" in aarch64) a=aarch64 ;; mips*) a=mipsel ;; *) return 0 ;; esac + command -v curl >/dev/null || return 0 + { curl -fsSL -m 30 -o /opt/bin/udptcp.tmp "https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/udptcp/bin/udptcp-$a" 2>/dev/null \ + || curl -fsSL -m 30 -o /opt/bin/udptcp.tmp "https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/udptcp/bin/udptcp-$a" 2>/dev/null; } \ + && [ -s /opt/bin/udptcp.tmp ] && chmod 755 /opt/bin/udptcp.tmp && mv /opt/bin/udptcp.tmp /opt/bin/udptcp \ + && log "PULL: udptcp installed ($a)" || rm -f /opt/bin/udptcp.tmp +} + # One fetch + compare + apply pass. Returns 0 always (best-effort). do_pull() { + ensure_udptcp # Try each source (tunnel first); accept first that returns a valid conf. got=0 for base in $PANEL_URLS; do @@ -113,7 +132,13 @@ apply_obf_params() { D=$(grep "^OBF_MAX_DUMMY=" "$CONF" 2>/dev/null | cut -d= -f2-) I=$(grep "^OBF_IDLE=" "$CONF" 2>/dev/null | cut -d= -f2-) changed=0 - for OBF in "$PHOBOS_DIR"/wg-obfuscator*.conf; do + # только свой обфускатор: у других учёток свои failover.conf + own="" + for l in "$PHOBOS_DIR"/wg-obfuscator*.link; do + [ "$(cat "$l" 2>/dev/null)" = "$CLIENT_ID" ] && own="${l%.link}.conf" + done + [ -z "$own" ] && [ -z "$PHOBOS_ACC" ] && own="$PHOBOS_DIR/wg-obfuscator*.conf" + for OBF in $own; do [ -f "$OBF" ] || continue cur=$(grep "^masking = " "$OBF" 2>/dev/null | sed 's/masking = //') [ "$cur" != "$M" ] && { sed -i "s/^masking = .*/masking = $M/" "$OBF"; changed=1; } @@ -129,7 +154,9 @@ apply_obf_params() { [ "$changed" = 0 ] && return 0 log "PULL: obf params applied (masking=$M dummy=${D:-} idle=${I:-}), restart obfuscator" if ls /opt/etc/init.d/S*obfuscator* >/dev/null 2>&1; then - for sc in /opt/etc/init.d/S*obfuscator*; do [ -x "$sc" ] && "$sc" restart >/dev/null 2>&1; done + for sc in /opt/etc/init.d/S*obfuscator*; do + for OBF in $own; do grep -q "/$(basename "$OBF")\"\$" "$sc" && [ -x "$sc" ] && "$sc" restart >/dev/null 2>&1; done + done elif command -v systemctl >/dev/null 2>&1; then systemctl restart 'wg-obfuscator*' >/dev/null 2>&1 || true else diff --git a/overlay/router-configure-wireguard.sh b/overlay/router-configure-wireguard.sh index f8bb787..0f4f4d5 100755 --- a/overlay/router-configure-wireguard.sh +++ b/overlay/router-configure-wireguard.sh @@ -278,11 +278,21 @@ EOF # Настраиваем статический маршрут в созданный интерфейс отдельным вызовом RCI local route_target_net=$(echo "$client_ip_addr" | cut -d'.' -f1-2).0.0 - log "Настройка маршрута для подсети туннеля (${route_target_net}/16) -> ${interface_name}..." + local route_mask="255.255.0.0" + # 02.11.2026: доп. учётка (на роутере уже стоит другая): та же /16 через второй + # интерфейс конфликтует с главной, а /32 до панели увёл бы pull главной в чужой + # туннель — маршрут не ставим, pull доп. учётки ходит по panel_urls. + local main_cid=$(cat /opt/etc/Phobos/client_id 2>/dev/null) + if [ -n "$main_cid" ] && [ "$main_cid" != "${CLIENT_NAME}" ]; then + log "Доп. учётка (главная — ${main_cid}): маршрут подсети туннеля не нужен" + log "Интерфейс ${interface_name} создан ✓" + return 0 + fi + log "Настройка маршрута для туннеля (${route_target_net}/${route_mask}) -> ${interface_name}..." local route_json=$(cat <\s*([A-Za-z][A-Za-z .'-]*?)\s*<", html) + return m.group(1) if m else None + except Exception: + return None + + +def google_geo(): + """v4/v6 = default route (what clients exit with); ips = every public IPv4 on the server.""" + import re as _re + import subprocess as _sp + import time as _t + out = {"v4": _google_country("-4"), "v6": _google_country("-6"), "ips": {}, "ts": int(_t.time())} + try: + for line in _sp.run(["ip", "-4", "-o", "addr", "show", "scope", "global"], capture_output=True, + text=True, timeout=10).stdout.splitlines(): + ip = line.split()[3].split("/")[0] + if not _re.match(r"^(10|127|172\.(1[6-9]|2\d|3[01])|192\.168|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7]))\.", ip): + out["ips"][ip] = _google_country("-4", "--interface", ip) + except Exception: + pass + return out + + +@app.route("/api/google-geo") +def api_google_geo(): + if not check_api_key(): + return jsonify({"error": "unauthorized"}), 401 + return jsonify(google_geo()) + + @app.route("/api/limits", methods=["POST"]) def set_limits(): """Per-client speed limits pushed by the main panel (" " lines).""" diff --git a/server/phobos-altip.sh b/server/phobos-altip.sh new file mode 100755 index 0000000..0e6e3d1 --- /dev/null +++ b/server/phobos-altip.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# PCA Phobos — второй/запасной IP сервера (09.33). +# wg-obfuscator и udptcp слушают 0.0.0.0, но отвечают с основного IP, а провайдер +# клиента может резать именно основной. DNAT «второй IP:порт → основной IP:порт» +# заставляет ответ уходить с того адреса, на который пришёл запрос. +# phobos-altip apply — правила для всех доп. IPv4 сервера (служба делает это при старте) +# phobos-altip status — что настроено +# phobos-altip enable — поставить службу phobos-altip +ENV=/opt/Phobos/server/server.env +[ -f "$ENV" ] || ENV=/opt/Phobos/server.env +get() { grep -E "^$1=" "$ENV" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"; } +MAIN=$(get SERVER_PUBLIC_IP_V4) +PORTS=$(get OBFUSCATOR_PORTS); [ -n "$PORTS" ] || PORTS=2083,5443,993 +TCP=$(get TCP_PORT) +alts() { ip -4 -o addr show scope global | awk '{split($4,a,"/"); print a[1]}' | grep -vxF "$MAIN" | grep -v '^10\.\|^172\.\(1[6-9]\|2[0-9]\|3[01]\)\.\|^192\.168\.'; } +rule() { # $1 proto $2 alt $3 port + iptables -t nat -C PREROUTING -d "$2" -p "$1" --dport "$3" -j DNAT --to-destination "$MAIN:$3" 2>/dev/null \ + || iptables -t nat -I PREROUTING -d "$2" -p "$1" --dport "$3" -j DNAT --to-destination "$MAIN:$3" +} +case "$1" in + apply) + [ -n "$MAIN" ] || { echo "SERVER_PUBLIC_IP_V4 not set"; exit 1; } + for a in $(alts); do + for p in $(echo "$PORTS" | tr ',' ' '); do rule udp "$a" "$p"; done + [ -n "$TCP" ] && rule tcp "$a" "$TCP" + echo "alt $a -> $MAIN (udp $PORTS${TCP:+, tcp $TCP})" + done ;; + status) echo "main: $MAIN"; echo "alt: $(alts | tr '\n' ' ')"; iptables -t nat -S PREROUTING | grep -- "--to-destination $MAIN:" ;; + enable) + cat > /etc/systemd/system/phobos-altip.service <), +# главная — прямо в $PHOBOS_DIR. Конфиги обфускаторов общие, свой ищем по *.link. +ACC_DIR="${PHOBOS_ACC:-$PHOBOS_DIR}" +CONF="$ACC_DIR/failover.conf" +STATE="$ACC_DIR/state" +LOG="$ACC_DIR/health.log" +CID=$(cat "$ACC_DIR/client_id" 2>/dev/null) OBF_CONF="$PHOBOS_DIR/wg-obfuscator.conf" -LOCKFILE="/tmp/phobos-health.lock" +for l in "$PHOBOS_DIR"/wg-obfuscator*.link; do + [ -n "$CID" ] && [ "$(cat "$l" 2>/dev/null)" = "$CID" ] && OBF_CONF="${l%.link}.conf" && break +done +LOCKFILE="/tmp/phobos-health${PHOBOS_ACC:+-$CID}.lock" WG_IF="Wireguard3" MAX_LOG_LINES=200 @@ -25,9 +33,9 @@ PRIMARY_CHECK_INTERVAL=300 # wg-obfuscator-* services are active — the bare panel port stays up even # when the tunnel path is dead, so we must NOT switch back on panel liveness # alone (that caused premature switchback to a dead primary). -PRIMARY_PROBE_SCHEME="${PRIMARY_PROBE_SCHEME:-$(cat "$PHOBOS_DIR/panel_scheme" 2>/dev/null)}" +PRIMARY_PROBE_SCHEME="${PRIMARY_PROBE_SCHEME:-$(cat "$ACC_DIR/panel_scheme" 2>/dev/null)}" PRIMARY_PROBE_SCHEME="${PRIMARY_PROBE_SCHEME:-http}" -PRIMARY_PROBE_PORT="${PRIMARY_PROBE_PORT:-$(cat "$PHOBOS_DIR/panel_port" 2>/dev/null)}" +PRIMARY_PROBE_PORT="${PRIMARY_PROBE_PORT:-$(cat "$ACC_DIR/panel_port" 2>/dev/null)}" PRIMARY_PROBE_PORT="${PRIMARY_PROBE_PORT:-8443}" PRIMARY_PROBE_PATH="/api/obf-health" # Connectivity check targets (logged for context only) @@ -55,6 +63,16 @@ mkdir -p "$STATE" # ── Detect WG interface ── detect_wg_interface() { + # точное совпадение "Phobos-<клиент>"; доп. учётка не должна схватить чужой интерфейс + for i in 0 1 2 3 4 5 6 7 8 9; do + desc=$(ndmc -c "show interface Wireguard${i}" 2>/dev/null | grep description | head -1) + # HydraRoute Neo names the interface by the bare client id (09.33) + if [ -n "$CID" ] && echo "$desc" | grep -qE "(Phobos-|: )${CID}\$"; then + WG_IF="Wireguard${i}" + return 0 + fi + done + [ -n "$PHOBOS_ACC" ] && return 1 for i in 0 1 2 3 4 5 6 7 8 9; do desc=$(ndmc -c "show interface Wireguard${i}" 2>/dev/null | grep description | head -1) if echo "$desc" | grep -qi phobos; then @@ -65,6 +83,19 @@ detect_wg_interface() { return 1 } +# ── Restart own obfuscator (init script whose ARGS name our config) ── +restart_obf() { + sc=$(grep -l "/$(basename "$OBF_CONF")\"\$" /opt/etc/init.d/S*obfuscator* 2>/dev/null | head -1) + [ -z "$sc" ] && [ -f /opt/etc/init.d/S49wg-obfuscator ] && sc=/opt/etc/init.d/S49wg-obfuscator + if [ -n "$sc" ]; then + "$sc" restart >/dev/null 2>&1 + else + killall wg-obfuscator 2>/dev/null + sleep 1 + wg-obfuscator --config "$OBF_CONF" & + fi +} + # ── Read failover config ── if [ ! -f "$CONF" ]; then log "ERROR: no failover.conf" @@ -80,6 +111,7 @@ while true; do eval "SERVER_${idx}_PORTS=$(echo "$val" | cut -d: -f2-)" eval "SERVER_${idx}_KEY=$(grep "^KEY_${idx}=" "$CONF" 2>/dev/null | cut -d= -f2-)" eval "SERVER_${idx}_WGKEY=$(grep "^WGKEY_${idx}=" "$CONF" 2>/dev/null | cut -d= -f2-)" + eval "SERVER_${idx}_TCP=$(grep "^TCP_${idx}=" "$CONF" 2>/dev/null | cut -d= -f2-)" SERVER_COUNT=$idx idx=$((idx + 1)) done @@ -94,14 +126,26 @@ CURRENT_SERVER=$(cat "$STATE/current_server" 2>/dev/null || echo "1") CURRENT_PORT_IDX=$(cat "$STATE/current_port_idx" 2>/dev/null || echo "0") FAIL_COUNT=$(cat "$STATE/fail_count" 2>/dev/null || echo "0") PRIMARY_CHECK_TS=$(cat "$STATE/primary_check_ts" 2>/dev/null || echo "0") +TCP_MODE=$(cat "$STATE/tcp_mode" 2>/dev/null || echo "0") # 0 = UDP, N = TCP via server N +CYCLES=$(cat "$STATE/udp_cycles" 2>/dev/null || echo "0") # full UDP rounds without success +UDPTCP_BIN=/opt/bin/udptcp +UDPTCP_LPORT=13256 -# ── Detect Phobos WG interface (non-fatal, fallback to Wireguard3) ── -# Нет своего интерфейса — выходим: запасной Wireguard3 бывает чужим туннелем. +# ── Detect Phobos WG interface ── +# Нет своего интерфейса — выходим: запасной Wireguard3 бывает чужим (так сторож +# удалённой доп. учётки вписал пир Phobos в посторонний туннель). detect_wg_interface || { log "ERROR: own WG interface not found, skip" exit 0 } +# ── Panel tunnel IP must go through our tunnel (HydraRoute-made interface has no +# such route → pull went to WAN and hung, 09.33) ── +PT_IP=$(cat "$ACC_DIR/panel_tunnel_ip" 2>/dev/null) +if [ -n "$PT_IP" ] && ip route get "$PT_IP" 2>/dev/null | grep -q "dev ppp\|dev eth\|dev wan"; then + ndmc -c "ip route $PT_IP 255.255.255.255 $WG_IF auto" >/dev/null 2>&1 && log "ROUTE: $PT_IP via $WG_IF" +fi + # ── Get handshake age via ndmc ── # A peer switch can leave the removed peer's stale handshake listed in ndmc # output (often a huge sentinel like 2147483647). Take the FRESHEST (minimum @@ -151,9 +195,13 @@ list_iface_keys() { count_server_peers() { present=$(list_iface_keys) n=0 + seen=" " i=1 while [ "$i" -le "$SERVER_COUNT" ]; do eval "wk=\$SERVER_${i}_WGKEY" + # one server may have several addresses = same key in several entries + case "$seen" in *" $wk "*) i=$((i + 1)); continue ;; esac + seen="$seen$wk " if [ -n "$wk" ] && echo "$present" | grep -q "^${wk}$"; then n=$((n + 1)) fi @@ -194,7 +242,7 @@ switch_wg_peer() { # 01.11.2026: порт — из конфига обфускатора (было зашито 13255, а установщик # берёт свободный, на части роутеров 51820 → после переключения туннель мёртв). # Через ndmc по шагам: JSON-RCI с allow-ips "::"/0 молча не ставил allow-ips. - lport=$(grep 'source-lport' "$PHOBOS_DIR/wg-obfuscator.conf" 2>/dev/null | tr -d ' ' | cut -d'=' -f2) + lport=$(grep 'source-lport' "$OBF_CONF" 2>/dev/null | tr -d ' ' | cut -d'=' -f2) lport=${lport:-13255} ndmc -c "interface $WG_IF wireguard peer $new_key" >/dev/null 2>&1 ndmc -c "interface $WG_IF wireguard peer $new_key endpoint 127.0.0.1:$lport" >/dev/null 2>&1 @@ -226,6 +274,7 @@ switch_endpoint() { fi log "SWITCH → server $server_idx ($host:$port)" + stop_tcp # 1. Switch WG peer key if different server switch_wg_peer "$wg_key" @@ -239,13 +288,7 @@ switch_endpoint() { fi # 3. Restart obfuscator - if [ -f /opt/etc/init.d/S49wg-obfuscator ]; then - /opt/etc/init.d/S49wg-obfuscator restart >/dev/null 2>&1 - else - killall wg-obfuscator 2>/dev/null - sleep 1 - wg-obfuscator --config "$OBF_CONF" & - fi + restart_obf # 4. Save state (fail_count is managed by the caller, NOT reset here — # resetting on a port-hop would prevent escalation to server failover) @@ -253,6 +296,45 @@ switch_endpoint() { echo "$port_idx" > "$STATE/current_port_idx" } +# ── TCP channel (09.33/1.5.0): obfuscated UDP inside TCP via local udptcp ── +stop_tcp() { + [ "$TCP_MODE" = "0" ] && ! pidof udptcp >/dev/null 2>&1 && return 0 + killall udptcp 2>/dev/null + TCP_MODE=0 + echo 0 > "$STATE/tcp_mode" +} + +# start_tcp N — tunnel through server N's TCP port; 1 = started +start_tcp() { + n=$1 + eval "host=\$SERVER_${n}_HOST" + eval "tport=\$SERVER_${n}_TCP" + eval "obf_key=\$SERVER_${n}_KEY" + eval "wg_key=\$SERVER_${n}_WGKEY" + [ -n "$tport" ] && [ -x "$UDPTCP_BIN" ] || return 1 + killall udptcp 2>/dev/null + "$UDPTCP_BIN" -c "127.0.0.1:${UDPTCP_LPORT}" "${host}:${tport}" >/dev/null 2>&1 & + switch_wg_peer "$wg_key" + sed -i "s|^target = .*|target = 127.0.0.1:${UDPTCP_LPORT}|" "$OBF_CONF" + [ -n "$obf_key" ] && sed -i "s|^key = .*|key = ${obf_key}|" "$OBF_CONF" + restart_obf + TCP_MODE=$n + echo "$n" > "$STATE/tcp_mode" + echo "$n" > "$STATE/current_server" + log "TCP MODE → server $n (${host}:${tport} over TCP)" + return 0 +} + +# try_tcp_after N — first server after N (no wrap) that has a TCP port; 1 = none +try_tcp_after() { + i=$(( $1 + 1 )) + while [ "$i" -le "$SERVER_COUNT" ]; do + start_tcp "$i" && return 0 + i=$((i + 1)) + done + return 1 +} + # ── Try next port on current server ── try_next_port() { port_count=$(get_port_count "$CURRENT_SERVER") @@ -267,7 +349,9 @@ try_next_port() { # ── Try next server ── try_next_server() { next=$((CURRENT_SERVER + 1)) - [ "$next" -gt "$SERVER_COUNT" ] && next=1 + if [ "$next" -gt "$SERVER_COUNT" ]; then + next=1 + fi [ "$next" -eq "$CURRENT_SERVER" ] && return 1 log "FAILOVER → server $next" @@ -277,7 +361,7 @@ try_next_server() { # ── Check if primary is back ── check_primary() { - [ "$CURRENT_SERVER" -eq 1 ] && return + [ "$CURRENT_SERVER" -eq 1 ] && [ "$TCP_MODE" = "0" ] && return now=$(date +%s) elapsed=$((now - PRIMARY_CHECK_TS)) @@ -299,7 +383,10 @@ check_primary() { fi done if [ -n "$ok_url" ]; then - log "PRIMARY ($ok_url http=200) alive, switching back" + # HTTP alive does not mean UDP passes (провайдер режет UDP к IP, 09.33): + # пробуем, а если рукопожатия не будет — trial_check вернёт как было. + log "PRIMARY ($ok_url http=200) alive, TRIAL switch back" + echo "$CURRENT_SERVER $CURRENT_PORT_IDX $TCP_MODE $(date +%s)" > "$STATE/trial" switch_endpoint 1 0 echo "0" > "$STATE/fail_count" else @@ -341,6 +428,8 @@ heal_lan_routing() { while read -r mac pol; do [ -z "$mac" ] && continue case "$mac" in \#*) continue;; esac + # политику удалили — не воскрешаем привязку к ней + echo "$rc" | grep -qx "ip policy $pol" || continue if ! echo "$rc" | grep -q "host $mac policy $pol"; then ndmc -c "ip hotspot host $mac policy $pol" >/dev/null 2>&1 log "HEAL: re-bound host $mac -> $pol" @@ -417,12 +506,31 @@ fi AGE=$(get_handshake_age) WAN=$(check_connectivity && echo "yes" || echo "no") +# ── Trial of the primary: fresh handshake within ~2 min or go back ── +if [ -f "$STATE/trial" ]; then + read t_srv t_port t_tcp t_ts < "$STATE/trial" + if [ "$AGE" -lt "$HANDSHAKE_WARN" ]; then + log "TRIAL ok: primary works" + rm -f "$STATE/trial"; echo 0 > "$STATE/udp_cycles" + elif [ $(( $(date +%s) - ${t_ts:-0} )) -ge 110 ]; then + rm -f "$STATE/trial" + log "TRIAL failed: back to server $t_srv" + if [ "${t_tcp:-0}" != "0" ]; then start_tcp "$t_tcp" || switch_endpoint "$t_srv" "$t_port" + else switch_endpoint "$t_srv" "$t_port"; fi + echo 0 > "$STATE/fail_count" + exit 0 + else + exit 0 # give the trial time + fi +fi + # Tunnel healthy = fresh handshake if [ "$AGE" -lt "$HANDSHAKE_WARN" ]; then if [ "$FAIL_COUNT" -gt 0 ]; then log "OK: tunnel up (handshake=${AGE}s, server=$CURRENT_SERVER, wan=$WAN)" echo "0" > "$STATE/fail_count" fi + echo 0 > "$STATE/udp_cycles" check_primary exit 0 fi @@ -438,23 +546,30 @@ log "STALE: handshake=${AGE}s wan=${WAN} server=$CURRENT_SERVER port=$CURRENT_PO # Stage 1 (fail 1): Restart obfuscator if [ "$FAIL_COUNT" -eq 1 ]; then log "ACTION: restart obfuscator" - if [ -f /opt/etc/init.d/S49wg-obfuscator ]; then - /opt/etc/init.d/S49wg-obfuscator restart >/dev/null 2>&1 - else - killall wg-obfuscator 2>/dev/null - sleep 1 - wg-obfuscator --config "$OBF_CONF" & - fi + restart_obf exit 0 fi -# Stage 2 (fail 2): Port hop to an alternate port on the SAME server -if [ "$FAIL_COUNT" -eq 2 ]; then +# TCP mode went stale → back to the UDP round (next server) +if [ "$TCP_MODE" != "0" ] && [ "$FAIL_COUNT" -ge 2 ]; then + log "TCP stale on server $TCP_MODE" + if try_tcp_after "$TCP_MODE"; then echo 0 > "$STATE/fail_count"; exit 0; fi + stop_tcp + switch_endpoint 1 0; echo 0 > "$STATE/fail_count"; exit 0 +fi + +# Stage 2 (fail 2+): hop through ALL ports of the SAME server (was: only one) +if [ "$FAIL_COUNT" -ge 2 ]; then if try_next_port; then exit 0 fi - # only one port → fall through to server failover - log "single port, escalate to failover" + log "ports of server $CURRENT_SERVER exhausted, escalate to failover" +fi + +# Whole UDP round failed (all servers × ports) → TCP, if any server offers it +if [ "$CURRENT_SERVER" -eq "$SERVER_COUNT" ]; then + CYCLES=$((CYCLES + 1)); echo "$CYCLES" > "$STATE/udp_cycles" + if try_tcp_after 0; then echo 0 > "$STATE/fail_count"; exit 0; fi fi # Stage 3 (fail 3+): Server failover. Reset fail_count so the new server diff --git a/server/phobos-pull.sh b/server/phobos-pull.sh index f6eebbc..014c3e7 100755 --- a/server/phobos-pull.sh +++ b/server/phobos-pull.sh @@ -1,4 +1,5 @@ #!/bin/sh +export PATH=/opt/bin:/opt/sbin:/opt/usr/bin:$PATH # cron без /opt/bin -> busybox wget висел вечно (09.29) # ============================================================ # Phobos Config Pull — NAT-friendly management channel. # Runs ON the router via cron (every 2 min). OUTBOUND ONLY: @@ -21,9 +22,11 @@ # PANEL env or default below -> panel base URL # ============================================================ PHOBOS_DIR="/opt/etc/Phobos" -CONF="$PHOBOS_DIR/failover.conf" +# 02.11.2026: доп. учётки — PHOBOS_ACC=$PHOBOS_DIR/acc/<клиент> (см. phobos-health.sh) +ACC_DIR="${PHOBOS_ACC:-$PHOBOS_DIR}" +CONF="$ACC_DIR/failover.conf" HEALTH="$PHOBOS_DIR/phobos-health.sh" -LOG="$PHOBOS_DIR/health.log" +LOG="$ACC_DIR/health.log" # Config sources, tried in order. TUNNEL FIRST: panel_tunnel_ip is the wg0 of # whatever server the tunnel currently terminates on, so management rides the # same obfuscated channel as data — survives a public-IP ban and follows @@ -31,15 +34,18 @@ LOG="$PHOBOS_DIR/health.log" # - panel_tunnel_ip:8444 -> secondary server agent (phobos-api) # - panel_tunnel_ip: -> primary server panel # - public panel IP -> bootstrap / if tunnel is down -PANEL_TUNNEL_IP="${PANEL_TUNNEL_IP:-$(cat "$PHOBOS_DIR/panel_tunnel_ip" 2>/dev/null)}" +PANEL_TUNNEL_IP="${PANEL_TUNNEL_IP:-$(cat "$ACC_DIR/panel_tunnel_ip" 2>/dev/null)}" [ -n "$PANEL_TUNNEL_IP" ] || PANEL_TUNNEL_IP="10.25.0.1" -PANEL="${PANEL:-$(cat "$PHOBOS_DIR/panel_url" 2>/dev/null)}" -PANEL_URLS="${PANEL_URLS:-$(cat "$PHOBOS_DIR/panel_urls" 2>/dev/null)}" +PANEL="${PANEL:-$(cat "$ACC_DIR/panel_url" 2>/dev/null)}" +PANEL_URLS="${PANEL_URLS:-$(cat "$ACC_DIR/panel_urls" 2>/dev/null)}" [ -n "$PANEL_URLS" ] || PANEL_URLS="http://${PANEL_TUNNEL_IP}:8444 $PANEL" -CLIENT_ID=$(cat "$PHOBOS_DIR/client_id" 2>/dev/null || echo "home") -TOKEN=$(cat "$PHOBOS_DIR/pull_token" 2>/dev/null) -TMP="/tmp/failover.conf.pull" -LOCK="/tmp/phobos-pull.lock" +# + public URLs the panel announced in failover.conf (second IP etc., 09.33) +_extra=$(grep "^PANEL_URLS_EXTRA=" "$CONF" 2>/dev/null | cut -d= -f2-) +for _u in $_extra; do case " $PANEL_URLS " in *" $_u "*) ;; *) PANEL_URLS="$PANEL_URLS $_u" ;; esac; done +CLIENT_ID=$(cat "$ACC_DIR/client_id" 2>/dev/null || echo "home") +TOKEN=$(cat "$ACC_DIR/pull_token" 2>/dev/null) +TMP="/tmp/failover.conf.pull${PHOBOS_ACC:+-$CLIENT_ID}" +LOCK="/tmp/phobos-pull${PHOBOS_ACC:+-$CLIENT_ID}.lock" log() { echo "$(date '+%H:%M:%S') $1" >> "$LOG"; } @@ -74,8 +80,21 @@ fetch_config() { wget -T 8 --no-check-certificate -q -O "$TMP" "$url" 2>/dev/null || wget -T 8 -q -O "$TMP" "$url" 2>/dev/null } +# TCP channel for routers (1.5.0): the guard needs /opt/bin/udptcp when failover.conf has TCP_N. +ensure_udptcp() { + [ -x /opt/bin/udptcp ] && return 0 + grep -q "^TCP_[0-9]*=" "$CONF" 2>/dev/null || return 0 + case "$(uname -m)" in aarch64) a=aarch64 ;; mips*) a=mipsel ;; *) return 0 ;; esac + command -v curl >/dev/null || return 0 + { curl -fsSL -m 30 -o /opt/bin/udptcp.tmp "https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/udptcp/bin/udptcp-$a" 2>/dev/null \ + || curl -fsSL -m 30 -o /opt/bin/udptcp.tmp "https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/udptcp/bin/udptcp-$a" 2>/dev/null; } \ + && [ -s /opt/bin/udptcp.tmp ] && chmod 755 /opt/bin/udptcp.tmp && mv /opt/bin/udptcp.tmp /opt/bin/udptcp \ + && log "PULL: udptcp installed ($a)" || rm -f /opt/bin/udptcp.tmp +} + # One fetch + compare + apply pass. Returns 0 always (best-effort). do_pull() { + ensure_udptcp # Try each source (tunnel first); accept first that returns a valid conf. got=0 for base in $PANEL_URLS; do @@ -102,6 +121,48 @@ do_pull() { if [ "$old_s1" != "$new_s1" ] && [ -f "$HEALTH" ]; then sh "$HEALTH" apply-server 1 fi + + apply_obf_params +} + +# Apply obfuscator params (masking/max-dummy/idle) delivered in failover.conf. +apply_obf_params() { + M=$(grep "^OBF_MASKING=" "$CONF" 2>/dev/null | cut -d= -f2-) + [ -z "$M" ] && return 0 + D=$(grep "^OBF_MAX_DUMMY=" "$CONF" 2>/dev/null | cut -d= -f2-) + I=$(grep "^OBF_IDLE=" "$CONF" 2>/dev/null | cut -d= -f2-) + changed=0 + # только свой обфускатор: у других учёток свои failover.conf + own="" + for l in "$PHOBOS_DIR"/wg-obfuscator*.link; do + [ "$(cat "$l" 2>/dev/null)" = "$CLIENT_ID" ] && own="${l%.link}.conf" + done + [ -z "$own" ] && [ -z "$PHOBOS_ACC" ] && own="$PHOBOS_DIR/wg-obfuscator*.conf" + for OBF in $own; do + [ -f "$OBF" ] || continue + cur=$(grep "^masking = " "$OBF" 2>/dev/null | sed 's/masking = //') + [ "$cur" != "$M" ] && { sed -i "s/^masking = .*/masking = $M/" "$OBF"; changed=1; } + if [ -n "$D" ]; then + cur=$(grep "^max-dummy = " "$OBF" 2>/dev/null | sed 's/max-dummy = //') + [ "$cur" != "$D" ] && { sed -i "s/^max-dummy = .*/max-dummy = $D/" "$OBF"; changed=1; } + fi + if [ -n "$I" ]; then + cur=$(grep "^idle-timeout = " "$OBF" 2>/dev/null | sed 's/idle-timeout = //') + [ "$cur" != "$I" ] && { sed -i "s/^idle-timeout = .*/idle-timeout = $I/" "$OBF"; changed=1; } + fi + done + [ "$changed" = 0 ] && return 0 + log "PULL: obf params applied (masking=$M dummy=${D:-} idle=${I:-}), restart obfuscator" + if ls /opt/etc/init.d/S*obfuscator* >/dev/null 2>&1; then + for sc in /opt/etc/init.d/S*obfuscator*; do + for OBF in $own; do grep -q "/$(basename "$OBF")\"\$" "$sc" && [ -x "$sc" ] && "$sc" restart >/dev/null 2>&1; done + done + elif command -v systemctl >/dev/null 2>&1; then + systemctl restart 'wg-obfuscator*' >/dev/null 2>&1 || true + else + killall wg-obfuscator 2>/dev/null; sleep 1 + for OBF in "$PHOBOS_DIR"/wg-obfuscator*.conf; do [ -f "$OBF" ] && wg-obfuscator --config "$OBF" >/dev/null 2>&1 & done + fi } # Inner loop: cron fires this every 60s, but we poll ~4x per minute so a diff --git a/server/phobos-v6relay.py b/server/phobos-v6relay.py new file mode 100644 index 0000000..fcf994b --- /dev/null +++ b/server/phobos-v6relay.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""IPv6 -> local IPv4 UDP relay for the obfuscator (it speaks IPv4 only). + +Usage: phobos-v6relay.py LISTEN_PORT [TARGET_PORT] [TARGET_HOST] +One upstream socket per IPv6 client, so the obfuscator sees a stable +client address for the whole session. Idle clients are dropped after 300 s. +""" +import selectors +import socket +import sys +import time + +IDLE = 300 + +listen_port = int(sys.argv[1]) +target = (sys.argv[3] if len(sys.argv) > 3 else "127.0.0.1", + int(sys.argv[2]) if len(sys.argv) > 2 else listen_port) + +down = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) +down.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) +down.bind(("::", listen_port)) + +sel = selectors.DefaultSelector() +sel.register(down, selectors.EVENT_READ, None) +clients = {} # v6 addr -> [upstream socket, last seen] + +while True: + for key, _ in sel.select(timeout=30): + if key.data is None: + data, addr = down.recvfrom(65535) + c = clients.get(addr) + if c is None: + up = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + up.connect(target) + sel.register(up, selectors.EVENT_READ, addr) + c = clients[addr] = [up, 0] + c[1] = time.monotonic() + try: + c[0].send(data) + except OSError: + pass + else: + addr = key.data + try: + down.sendto(key.fileobj.recv(65535), addr) + clients[addr][1] = time.monotonic() + except OSError: + pass + now = time.monotonic() + for addr, (up, seen) in list(clients.items()): + if now - seen > IDLE: + sel.unregister(up) + up.close() + del clients[addr] diff --git a/update.sh b/update.sh index 3f1abb5..6f09c99 100755 --- a/update.sh +++ b/update.sh @@ -97,6 +97,7 @@ $PHOBOS_DIR/server/phobos-domain.sh|server/phobos-domain.sh $PHOBOS_DIR/server/phobos-limit.sh|server/phobos-limit.sh $PHOBOS_DIR/server/phobos-speedtest.sh|server/phobos-speedtest.sh $PHOBOS_DIR/server/phobos-tcp.sh|server/phobos-tcp.sh +$PHOBOS_DIR/server/phobos-altip.sh|server/phobos-altip.sh $PHOBOS_DIR/server/update.sh|update.sh LIST } @@ -113,6 +114,8 @@ ensure_tcp_fallback() { # $1=ref ; primary only (secondaries have no panel) [ -x "$PHOBOS_DIR/server/phobos-tcp.sh" ] && ln -sf "$PHOBOS_DIR/server/phobos-tcp.sh" /usr/local/bin/phobos-tcp [ -x "$PHOBOS_DIR/bin/udptcp" ] && "$PHOBOS_DIR/server/phobos-tcp.sh" enable >/dev/null 2>&1 \ && echo "TCP fallback: port $(grep '^TCP_PORT=' "$PHOBOS_DIR/server/server.env" | cut -d= -f2)" || true + # second IP answers from itself (09.33) — no-op on single-IP servers + [ -f "$PHOBOS_DIR/server/phobos-altip.sh" ] && sh "$PHOBOS_DIR/server/phobos-altip.sh" enable >/dev/null 2>&1 || true } run_self_check() {