diff --git a/dnsleak-guard/LICENSE b/dnsleak-guard/LICENSE new file mode 100644 index 0000000..898af02 --- /dev/null +++ b/dnsleak-guard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 KronoZ + +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/dnsleak-guard/README.md b/dnsleak-guard/README.md new file mode 100644 index 0000000..0eb926c --- /dev/null +++ b/dnsleak-guard/README.md @@ -0,0 +1,250 @@ +# dnsleak-guard + +Страж системного DNS для macOS. Автоматически возвращает DNS на `127.0.0.1`, если сторонняя программа (например, Clash Verge Rev) его перезаписывает. + +## 📖 Зачем это нужно + +**Clash Verge Rev** при запуске в режиме TUN принудительно прописывает `114.114.114.114` (китайский публичный DNS) в системные настройки macOS. Это приводит к: + +- **Утечке DNS** — провайдер перехватывает запросы к `114.114.114.114` и подставляет свои резолверы. +- **Рассинхрону** — часть DNS-запросов идёт через Clash TUN, часть — мимо. + +`dnsleak-guard` решает проблему: LaunchDaemon раз в 10 секунд проверяет системный DNS и возвращает его на `127.0.0.1`, где его перехватывает Clash TUN. + +## 📂 Состав + +| Файл | Назначение | +|---|---| +| `ensure_dns.sh` | Основной скрипт-страж. Работает от root, бесконечный цикл | +| `com.user.dnsleak-guard.plist` | LaunchDaemon для автозапуска при загрузке | +| `install.sh` | Скрипт автоустановки | + +## 🔧 Требования + +- macOS 11+ (Big Sur и новее) +- **Clash Verge Rev** или другой клиент в режиме TUN +- `dns.listen` в конфиге Clash должен слушать порт **53** (например, `0.0.0.0:53`) +- Права root (для `networksetup -setdnsservers`) + +## 📥 Установка + +### Автоматическая + +```bash +WIP +``` + +### Ручная + +```bash +# 1. Скрипт +sudo cp ensure_dns.sh /usr/local/bin/ensure_dns.sh +sudo chmod +x /usr/local/bin/ensure_dns.sh +sudo chown root:wheel /usr/local/bin/ensure_dns.sh + +# 2. LaunchDaemon +sudo cp com.user.dnsleak-guard.plist /Library/LaunchDaemons/ +sudo chown root:wheel /Library/LaunchDaemons/com.user.dnsleak-guard.plist +sudo chmod 644 /Library/LaunchDaemons/com.user.dnsleak-guard.plist + +# 3. Загрузить +sudo launchctl load /Library/LaunchDaemons/com.user.dnsleak-guard.plist + +# 4. Проверить +sudo launchctl list | grep dnsleak-guard +``` + +## 🚀 Использование + +После установки демон работает автоматически: + +- Запускается при загрузке системы (до логина пользователя). +- Каждые 10 секунд проверяет DNS на всех активных сетевых сервисах. +- Если DNS отличается от `127.0.0.1` — сбрасывает его и очищает DNS-кэш macOS. +- Пишет события в `/tmp/dnsleak-guard.log`. + +### Проверка состояния + +```bash +# Демон запущен? +sudo launchctl list | grep dnsleak-guard + +# Текущий системный DNS +scutil --dns | grep nameserver +# Должно быть: nameserver[0] : 127.0.0.1 + +# Логи работы +tail -20 /tmp/dnsleak-guard.log +``` + +### Ожидаемые логи + +``` +[2026-09-14 18:30:01] dnsleak-guard started +[2026-09-14 18:30:05] [Wi-Fi] DNS reset: '114.114.114.114' -> 127.0.0.1 +[2026-09-14 18:30:05] DNS cache flushed +``` + +Если в логах только `dnsleak-guard started` — значит, Clash не перезаписывает DNS, и страж просто ждёт. + +## 🛑 Управление + +```bash +# Остановить +sudo launchctl unload /Library/LaunchDaemons/com.user.dnsleak-guard.plist + +# Запустить +sudo launchctl load /Library/LaunchDaemons/com.user.dnsleak-guard.plist + +# Перезапустить (после правки скрипта) +sudo launchctl unload /Library/LaunchDaemons/com.user.dnsleak-guard.plist +sudo launchctl load /Library/LaunchDaemons/com.user.dnsleak-guard.plist +``` + +### Полное удаление + +```bash +sudo launchctl unload /Library/LaunchDaemons/com.user.dnsleak-guard.plist +sudo rm /Library/LaunchDaemons/com.user.dnsleak-guard.plist +sudo rm /usr/local/bin/ensure_dns.sh +sudo rm -f /tmp/dnsleak-guard.log /tmp/dnsleak-guard.out /tmp/dnsleak-guard.err + +# Вернуть автоматический DNS +sudo networksetup -setdnsservers Wi-Fi "Empty" +sudo networksetup -setdnsservers Ethernet "Empty" 2>/dev/null +sudo dscacheutil -flushcache +sudo killall -HUP mDNSResponder +``` + +## ⚙️ Конфигурация + +Параметры задаются в начале `ensure_dns.sh`: + +```bash +DNS_SERVER="127.0.0.1" # Целевой DNS-сервер +LOG_FILE="/tmp/dnsleak-guard.log" # Путь к логу +sleep 10 # Интервал проверки (в конце скрипта) +``` + +### Изменить интервал + +| Значение | Эффект | +|---|---| +| `sleep 5` | Быстрее реагирует, больше нагрузки | +| `sleep 10` | Компромисс (по умолчанию) | +| `sleep 30` | Реже проверяет, тише логи | +| `sleep 60` | Минимальная нагрузка | + +## 🐛 Решение проблем + +### Демон не запускается + +```bash +sudo launchctl list | grep dnsleak-guard +cat /tmp/dnsleak-guard.err +``` + +Если пусто — загрузите принудительно: + +```bash +sudo launchctl load -w /Library/LaunchDaemons/com.user.dnsleak-guard.plist +``` + +### Логи переполнены ошибками «X is not a recognized network service» + +Это псевдо-сервисы (Thunderbolt Bridge и т.д.). Убедитесь, что в скрипте используется `while IFS= read -r service`, а не `for service in $(...)`. Обновите скрипт: + +```bash +git pull +sudo cp ensure_dns.sh /usr/local/bin/ensure_dns.sh +sudo launchctl unload /Library/LaunchDaemons/com.user.dnsleak-guard.plist +sudo launchctl load /Library/LaunchDaemons/com.user.dnsleak-guard.plist +``` + +### DNS всё равно утекает + +**1. Проверьте, что Clash слушает порт 53:** + +```bash +sudo lsof -iTCP:53 -iUDP:53 | grep -iE "clash|mihomo" +``` + +Если пусто — в конфиге Clash замените `dns.listen` на `0.0.0.0:53`. + +**2. Отключите DoH в браузерах:** + +- Chrome: `chrome://settings/security` → «Использовать безопасный DNS» → **ВЫКЛ** +- Firefox: `about:preferences#privacy` → «Enable DNS over HTTPS» → **ВЫКЛ** + +**3. Сбросьте DNS-кэш:** + +```bash +sudo dscacheutil -flushcache +sudo killall -HUP mDNSResponder +``` + +**4. Проверьте через `dnsleaktest.com` → Extended Test.** Должен быть только IP вашего VPS. + +### Интернет пропадает после установки + +Скорее всего, **Clash не слушает порт 53**, а системный DNS указывает на `127.0.0.1`, где никто не отвечает. + +**Быстрое восстановление:** + +```bash +sudo networksetup -setdnsservers Wi-Fi "Empty" +sudo dscacheutil -flushcache +sudo killall -HUP mDNSResponder +``` + +Затем исправьте `dns.listen` в Clash. + +## 📊 Как это работает + +``` +┌─────────────────────────────────────────────────────────┐ +│ macOS │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ Системный DNS = 127.0.0.1 │ │ +│ └──────────────────┬────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ Clash TUN (utun) слушает 53 │ │ +│ │ dns-hijack: any:53 │ │ +│ └──────────────────┬────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ DNS → VLESS-туннель → VPS → CoreDNS │ │ +│ └───────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ LaunchDaemon com.user.dnsleak-guard │ +│ ├── Каждые 10 сек: проверяет DNS на всех сервисах │ +│ ├── Если DNS ≠ 127.0.0.1: │ +│ │ ├── networksetup -setdnsservers │ +│ │ └── dscacheutil -flushcache + killall mDNSResponder│ +│ └── Логи в /tmp/dnsleak-guard.log │ +└─────────────────────────────────────────────────────────┘ +``` + +## 🔐 Безопасность + +- Работает **от root** — необходимо для `networksetup -setdnsservers`. +- Не принимает внешних команд, только меняет DNS на заранее заданный `127.0.0.1`. +- Не передаёт данные наружу — вся работа локальная. +- Логи содержат только имена сервисов и их DNS — никакой чувствительной информации. + +## 📝 Лицензия + +MIT. + +## 🤝 Вклад + +Pull request'ы приветствуются. Особенно интересны: + +- Уведомления через Telegram / macOS Notification Center при сбросе DNS. +- Ротация логов. +- Совместимость с другими клиентами (ClashX Pro, sing-box, Stash). diff --git a/dnsleak-guard/com.user.dnsleak-guard.plist b/dnsleak-guard/com.user.dnsleak-guard.plist new file mode 100644 index 0000000..12176c3 --- /dev/null +++ b/dnsleak-guard/com.user.dnsleak-guard.plist @@ -0,0 +1,20 @@ + + + + + Label + com.user.dnsleak-guard + ProgramArguments + + /usr/local/bin/ensure_dns.sh + + RunAtLoad + + KeepAlive + + StandardOutPath + /tmp/dnsleak-guard.out + StandardErrorPath + /tmp/dnsleak-guard.err + + diff --git a/dnsleak-guard/ensure_dns.sh b/dnsleak-guard/ensure_dns.sh new file mode 100644 index 0000000..b9862ee --- /dev/null +++ b/dnsleak-guard/ensure_dns.sh @@ -0,0 +1,47 @@ +#!/bin/bash +DNS_SERVER="127.0.0.1" +LOG_FILE="/tmp/dnsleak-guard.log" + +exec 2>>"$LOG_FILE" + +if [ "$EUID" -ne 0 ]; then + echo "[$(date)] ERROR: must run as root" >> "$LOG_FILE" + exit 1 +fi + +log() { + echo "[$(date)] $1" >> "$LOG_FILE" +} + +log "dnsleak-guard started" + +while true; do + CHANGED=0 + + while IFS= read -r service; do + [[ "$service" == "An asterisk"* ]] && continue + [[ -z "$service" ]] && continue + [[ "$service" == "*"* ]] && continue + + current=$(networksetup -getdnsservers "$service" 2>/dev/null) + [ -z "$current" ] && continue + [[ "$current" == *"aren't any"* ]] && continue + + current_flat=$(echo "$current" | tr '\n' ' ' | sed 's/[[:space:]]*$//') + + if [ "$current_flat" != "$DNS_SERVER" ]; then + if networksetup -setdnsservers "$service" "$DNS_SERVER" 2>/dev/null; then + CHANGED=1 + log "[$service] DNS reset: '$current_flat' -> $DNS_SERVER" + fi + fi + done < <(networksetup -listallnetworkservices 2>/dev/null) + + if [ "$CHANGED" -eq 1 ]; then + dscacheutil -flushcache 2>/dev/null + killall -HUP mDNSResponder 2>/dev/null + log "DNS cache flushed" + fi + + sleep 10 +done diff --git a/dnsleak-guard/install.sh b/dnsleak-guard/install.sh new file mode 100644 index 0000000..d01804d --- /dev/null +++ b/dnsleak-guard/install.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e + +# Проверка root +if [ "$EUID" -ne 0 ]; then + echo "ERROR: run with sudo" + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "[1/4] Installing script to /usr/local/bin/ensure_dns.sh" +cp "$SCRIPT_DIR/ensure_dns.sh" /usr/local/bin/ensure_dns.sh +chmod +x /usr/local/bin/ensure_dns.sh +chown root:wheel /usr/local/bin/ensure_dns.sh + +echo "[2/4] Installing LaunchDaemon" +cp "$SCRIPT_DIR/com.user.dnsleak-guard.plist" /Library/LaunchDaemons/ +chown root:wheel /Library/LaunchDaemons/com.user.dnsleak-guard.plist +chmod 644 /Library/LaunchDaemons/com.user.dnsleak-guard.plist + +echo "[3/4] Loading LaunchDaemon" +launchctl unload /Library/LaunchDaemons/com.user.dnsleak-guard.plist 2>/dev/null || true +launchctl load /Library/LaunchDaemons/com.user.dnsleak-guard.plist + +echo "[4/4] Verifying" +sleep 2 +if launchctl list | grep -q "com.user.dnsleak-guard"; then + echo "✅ dnsleak-guard installed and running" + echo " Logs: /tmp/dnsleak-guard.log" + echo " Stop: sudo launchctl unload /Library/LaunchDaemons/com.user.dnsleak-guard.plist" +else + echo "❌ Failed to load daemon. Check /tmp/dnsleak-guard.err" + exit 1 +fi