#!/bin/bash # ru-bypass.sh — .ru трафик напрямую мимо Amnezia, всё остальное через VPN + kill switch # # Принцип: ipset + более специфичные маршруты имеют приоритет над amn0. # Kill switch (UFW) остаётся активным — не-.ru трафик при отвале Amnezia блокируется. # # Первый запуск устанавливает ipset, два systemd сервиса и NetworkManager dispatcher: # - ru-ipset-restore.service запускается ДО UFW, восстанавливает ipset из файла # - ru-bypass.service запускается после network-online, обновляет RIPE-список и маршруты # Каждый запуск обновляет список .ru IP-блоков из RIPE (кэш 24ч). # # Использование: sudo bash ru-bypass.sh GATEWAY="192.168.1.1" DEV="wlp1s0" SETNAME="ru-direct" CACHE="/var/cache/ru-delegations.txt" IPSET_SAVE="/etc/ipset.conf" RIPE_URL="https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-latest" SCRIPT_DEST="/usr/local/bin/ru-bypass.sh" UFW_BEFORE="/etc/ufw/before.rules" if [ "$(id -u)" != "0" ]; then echo "Запускай от root: sudo bash $0" exit 1 fi # --- Первичная настройка (однократно) --- if ! command -v ipset >/dev/null 2>&1; then echo "Устанавливаем ipset..." apt-get install -y ipset fi # Копируем скрипт в /usr/local/bin (нужно для systemd + NM dispatcher) SELF=$(realpath "$0") if [ "$SELF" != "$SCRIPT_DEST" ]; then cp "$SELF" "$SCRIPT_DEST" chmod +x "$SCRIPT_DEST" echo "Скрипт скопирован в $SCRIPT_DEST" fi # Сервис восстановления ipset ДО старта UFW (однократно) RESTORE_SVC="/etc/systemd/system/ru-ipset-restore.service" if [ ! -f "$RESTORE_SVC" ]; then cat > "$RESTORE_SVC" < "$BYPASS_SVC" <<'EOF' [Unit] Description=Route .ru IP blocks directly (bypass Amnezia VPN) After=network-online.target ru-ipset-restore.service Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/ru-bypass.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable ru-bypass.service echo "Сервис ru-bypass установлен." fi # NetworkManager dispatcher — авто-перезапуск когда amn0 поднимается (однократно) NM_DISPATCHER="/etc/NetworkManager/dispatcher.d/99-ru-bypass" if [ ! -f "$NM_DISPATCHER" ]; then cat > "$NM_DISPATCHER" <<'EOF' #!/bin/bash [ "$1" = "amn0" ] && [ "$2" = "up" ] && exec /usr/local/bin/ru-bypass.sh EOF chmod +x "$NM_DISPATCHER" echo "NetworkManager dispatcher установлен." fi # --- Обновляем RIPE-список (кэш 24ч) --- if [ ! -f "$CACHE" ] || [ $(( $(date +%s) - $(stat -c %Y "$CACHE" 2>/dev/null || echo 0) )) -gt 86400 ]; then echo "Обновляем список .ru IP-блоков из RIPE..." if curl -fsS -o "$CACHE.tmp" "$RIPE_URL"; then mv "$CACHE.tmp" "$CACHE" else echo "Предупреждение: не удалось скачать RIPE-список" if [ ! -f "$CACHE" ]; then exit 1; fi echo "Используем старый кэш от $(date -r "$CACHE")" fi fi # --- Создаём/обновляем ipset --- echo "Обновляем ipset $SETNAME..." # create -exist: не падает если уже есть (UFW на него ссылается, destroy ломает цепочку) ipset create "$SETNAME" hash:net -exist # flush: очищаем записи, но сохраняем сам set (iptables-правило остаётся валидным) ipset flush "$SETNAME" python3 -c " import ipaddress entries = 0 with open('$CACHE') as f_in: for line in f_in: parts = line.strip().split('|') if len(parts) < 5 or parts[1] != 'RU' or parts[2] != 'ipv4': continue ip_str, count = parts[3], int(parts[4]) first = ipaddress.IPv4Address(ip_str) last = first + count - 1 for net in ipaddress.summarize_address_range(first, last): print(f'add $SETNAME {net}') entries += 1 import sys; print(f'# entries: {entries}', file=sys.stderr) " 2>/tmp/ru-ipset-count.txt | ipset restore -exist -quiet ENTRIES=$(ipset list "$SETNAME" 2>/dev/null | grep -c '/') echo "ipset обновлён: $ENTRIES записей" # Сохраняем ipset на диск — ru-ipset-restore.service восстановит его до UFW при перезагрузке ipset save "$SETNAME" > "$IPSET_SAVE" echo "ipset сохранён в $IPSET_SAVE" # --- Добавляем маршруты --- echo "Добавляем маршруты..." python3 -c " import ipaddress with open('$CACHE') as f, open('/tmp/ru-routes.batch', 'w') as out: count = 0 for line in f: parts = line.strip().split('|') if len(parts) < 5 or parts[1] != 'RU' or parts[2] != 'ipv4': continue ip_str, n = parts[3], int(parts[4]) first = ipaddress.IPv4Address(ip_str) last = first + n - 1 for net in ipaddress.summarize_address_range(first, last): out.write(f'route replace {net} via $GATEWAY dev $DEV\n') count += 1 print(f'Маршрутов: {count}') " ip -force -batch /tmp/ru-routes.batch 2>/dev/null # --- Правило в UFW before.rules (однократно, после создания ipset) --- UFW_MARKER="match-set $SETNAME" if ! grep -q "$UFW_MARKER" "$UFW_BEFORE" 2>/dev/null; then echo "Добавляем правило в UFW before.rules..." sed -i "0,/^COMMIT/{s/^COMMIT/# .ru bypass (ipset $SETNAME)\n-A ufw-before-output -m set --match-set $SETNAME dst -o $DEV -j ACCEPT\nCOMMIT/}" "$UFW_BEFORE" if ufw status | grep -qE "активен|active"; then ufw reload fi echo "UFW обновлён." fi echo "" echo "Готово." echo " ip route get 8.8.8.8 -> dev amn0 (через VPN)" echo " ip route get 95.173.136.1 -> dev $DEV (напрямую)"