feat: ru-bypass — .ru трафик напрямую мимо Amnezia, всё остальное через VPN
- ipset ru-direct + маршруты через 192.168.1.1 для всех RU IP-блоков (RIPE) - kill switch (UFW) остаётся: не-.ru трафик при отвале Amnezia блокируется - ru-ipset-restore.service стартует до UFW — исправляет проблему перезагрузки - ru-bypass.service стартует после network-online — обновляет RIPE и маршруты - NM dispatcher авто-перезапускает при reconnect amn0 - python3 summarize_address_range для корректной обработки невыровненных блоков RIPE - tests/test_network.sh — автотесты маршрутизации и связности Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,8 @@ home-configs/
|
|||||||
│ └── SKILL.md
|
│ └── SKILL.md
|
||||||
├── network/
|
├── network/
|
||||||
│ ├── ks-off.sh # временно отключить UFW kill switch
|
│ ├── ks-off.sh # временно отключить UFW kill switch
|
||||||
│ └── ks-on.sh # восстановить UFW kill switch
|
│ ├── ks-on.sh # восстановить UFW kill switch
|
||||||
|
│ └── ru-bypass.sh # .ru трафик напрямую (bypass Amnezia), всё остальное через VPN
|
||||||
├── vless/
|
├── vless/
|
||||||
│ └── servers.conf # список VLESS-серверов для прокси
|
│ └── servers.conf # список VLESS-серверов для прокси
|
||||||
├── proxychains/
|
├── proxychains/
|
||||||
|
|||||||
182
home-configs/network/ru-bypass.sh
Normal file
182
home-configs/network/ru-bypass.sh
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/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 "Добавляем маршруты..."
|
||||||
|
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 (напрямую)"
|
||||||
82
tests/test_network.sh
Normal file
82
tests/test_network.sh
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Тесты сетевой настройки: Amnezia + ru-bypass + kill switch
|
||||||
|
# Запускать без sudo (проверяет что доступно обычному пользователю)
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GRN='\033[0;32m'
|
||||||
|
YEL='\033[0;33m'
|
||||||
|
CLR='\033[0m'
|
||||||
|
|
||||||
|
pass=0 fail=0
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local desc="$1" expected="$2"
|
||||||
|
local actual
|
||||||
|
actual=$(eval "$3" 2>&1)
|
||||||
|
if echo "$actual" | grep -qE "$expected"; then
|
||||||
|
echo -e "${GRN}✓${CLR} $desc"
|
||||||
|
pass=$((pass+1))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${CLR} $desc"
|
||||||
|
echo " ожидалось: $expected"
|
||||||
|
echo " получено: $(echo "$actual" | head -3)"
|
||||||
|
fail=$((fail+1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== 1. Проверка окружения ==="
|
||||||
|
check "Amnezia интерфейс (amn0) существует" "amn0" "ip link show amn0 2>/dev/null"
|
||||||
|
check "wlp1s0 wifi интерфейс" "wlp1s0" "ip link show wlp1s0 2>/dev/null"
|
||||||
|
|
||||||
|
IPSET_INFO=$(sudo ipset list ru-direct 2>/dev/null)
|
||||||
|
if [ -n "$IPSET_INFO" ]; then
|
||||||
|
echo -e "${GRN}✓${CLR} ipset ru-direct существует"
|
||||||
|
IPSET_COUNT=$(echo "$IPSET_INFO" | grep -c '/')
|
||||||
|
if [ "$IPSET_COUNT" -gt 100 ]; then
|
||||||
|
echo -e "${GRN}✓${CLR} ipset не пуст ($IPSET_COUNT блоков)"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${CLR} ipset слишком мал ($IPSET_COUNT блоков)"
|
||||||
|
fi
|
||||||
|
RU_IP=$(echo "$IPSET_INFO" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||||
|
else
|
||||||
|
echo -e "${YEL}?${CLR} ipset — проверь с sudo"
|
||||||
|
RU_IP=$(dig +short ya.ru A | head -1)
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "=== 2. Маршрутизация .ru vs не-.ru ==="
|
||||||
|
check ".ru IP ($RU_IP) → НЕ через amn0" "wl[pi]" "ip route get $RU_IP 2>/dev/null"
|
||||||
|
check "8.8.8.8 → через amn0" "amn0" "ip route get 8.8.8.8 2>/dev/null"
|
||||||
|
check "1.1.1.1 → через amn0" "amn0" "ip route get 1.1.1.1 2>/dev/null"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 3. DNS резолвинг ==="
|
||||||
|
check "ozon.ru резолвится" "185\.73\." "dig +short ozon.ru A 2>/dev/null"
|
||||||
|
check "google.com резолвится" "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" "dig +short google.com A 2>/dev/null | head -1"
|
||||||
|
check "gosuslugi.ru резолвится" "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" "dig +short gosuslugi.ru A 2>/dev/null | head -1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 4. Связность (Amnezia вкл) ==="
|
||||||
|
check "google.com отвечает (VPN)" "HTTP" "curl -sI --max-time 5 https://google.com 2>&1 | head -1"
|
||||||
|
check "ozon.ru отвечает (прямо)" "HTTP" "curl -sI --max-time 5 https://ozon.ru 2>&1 | head -1"
|
||||||
|
check "gosuslugi.ru отвечает (прямо)" "HTTP" "curl -sI --max-time 5 https://gosuslugi.ru 2>&1 | head -1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 5. Инфраструктура (нужен sudo) ==="
|
||||||
|
UFW_STATUS=$(sudo ufw status 2>/dev/null)
|
||||||
|
if echo "$UFW_STATUS" | grep -qE "активен|active"; then
|
||||||
|
echo -e "${GRN}✓${CLR} UFW активен"
|
||||||
|
else
|
||||||
|
echo -e "${YEL}?${CLR} UFW — запусти с sudo: sudo ufw status"
|
||||||
|
fi
|
||||||
|
check "ru-bypass сервис есть" "ru-bypass" "systemctl list-unit-files 2>/dev/null | grep ru-bypass || echo 'OK (проверить с sudo)'"
|
||||||
|
check "NM dispatcher есть" "99-ru-bypass" "ls -la /etc/NetworkManager/dispatcher.d/99-ru-bypass 2>/dev/null"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 6. Краевые случаи ==="
|
||||||
|
check "api.anthropic.com → amn0" "amn0" "ip route get $(dig +short api.anthropic.com A | head -1) 2>/dev/null"
|
||||||
|
check "ya.ru → НЕ через amn0 (прямо)" "wl[pi]" "ip route get $(dig +short ya.ru A | head -1) 2>/dev/null"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo -e "Итого: ${GRN}$pass пройдено${CLR}, ${RED}$fail провалено${CLR}"
|
||||||
|
[ "$fail" -eq 0 ] && echo -e "${GRN}ВСЁ ОК${CLR}" || echo -e "${RED}ЕСТЬ ПРОБЛЕМЫ${CLR}"
|
||||||
Reference in New Issue
Block a user