Files
ai-setup/home-configs/network/ru-bypass.sh

184 lines
6.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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" <<EOF
[Unit]
Description=Restore ru-direct ipset before UFW starts
DefaultDependencies=no
Before=ufw.service network.target
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/sbin/ipset restore -exist -file $IPSET_SAVE
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable ru-ipset-restore.service
echo "Сервис ru-ipset-restore установлен (стартует до UFW)."
fi
# Основной сервис обновления маршрутов (однократно)
BYPASS_SVC="/etc/systemd/system/ru-bypass.service"
if [ ! -f "$BYPASS_SVC" ]; then
cat > "$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 "Добавляем маршруты..."
rm -f /tmp/ru-routes.batch
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 (напрямую)"