fix: kill switch — UFW before.rules с актуальным DEV, прямые iptables, /etc/hosts для *.eltex.loc и elph

Три корневые проблемы и их исправления:

1. MANAGE_BUILTINS=no в /etc/default/ufw — цепочка ufw-before-output
   не вызывалась из OUTPUT, правила before.rules не применялись.
   → автофикс no→yes + прямые правила iptables (не зависят от UFW).

2. UFW-правила создавались однократно по маркеру — при смене DEV
   (wlp1s0→enp4s0) продолжали ссылаться на старый интерфейс.
   → теперь при каждом запуске удаляются и пересоздаются с актуальным DEV.

3. DNS через VPN для локальных доменов возвращал внешние IP вместо
   внутренних (RFC1918) — трафик уходил в VPN и не достигал серверов.
   → /etc/hosts с фиксированными IP для *.eltex.loc, mattermost, elph.
   → замена dig +short на getent hosts (уважает /etc/hosts).

Добавлены built-in KILL_SWITCH_EXCEPTIONS:
  mattermost.eltex-co.ru elph.eltex-co.ru 10.80.0.15

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Виталий Никитенко
2026-06-08 12:44:20 +03:00
parent e955c928d3
commit d2bbcc7e33

View File

@@ -35,6 +35,17 @@ LOCAL_DNS="${LOCAL_DNS:-}"
AMNEZIA_SERVER="${AMNEZIA_SERVER:-}" AMNEZIA_SERVER="${AMNEZIA_SERVER:-}"
KILL_SWITCH_EXCEPTIONS="${KILL_SWITCH_EXCEPTIONS:-}" KILL_SWITCH_EXCEPTIONS="${KILL_SWITCH_EXCEPTIONS:-}"
# Базовые исключения, необходимые для работы корпоративных сервисов
# Добавляются автоматически, даже если не указаны в конфиге
_BUILTIN_EXCEPTIONS="mattermost.eltex-co.ru elph.eltex-co.ru 10.80.0.15"
for _exc in $_BUILTIN_EXCEPTIONS; do
case " $KILL_SWITCH_EXCEPTIONS " in
*" $_exc "*) ;;
*) KILL_SWITCH_EXCEPTIONS="$KILL_SWITCH_EXCEPTIONS $_exc" ;;
esac
done
KILL_SWITCH_EXCEPTIONS="${KILL_SWITCH_EXCEPTIONS# }"
# Сохраняем конфиг для будущих запусков (systemd, NM dispatcher) # Сохраняем конфиг для будущих запусков (systemd, NM dispatcher)
cat > /etc/ru-bypass.conf <<_CONF cat > /etc/ru-bypass.conf <<_CONF
GATEWAY="$GATEWAY" GATEWAY="$GATEWAY"
@@ -146,6 +157,33 @@ EOF
echo "NetworkManager dispatcher установлен." echo "NetworkManager dispatcher установлен."
fi fi
# --- Локальные хосты (фиксируем IP для доменов, которые должны резолвиться локально) ---
# Без этого DNS через VPN может вернуть внешний IP вместо внутреннего,
# и трафик пойдёт через VPN вместо прямого соединения.
HOSTS_MARKER="# ru-bypass: local hosts"
# Удаляем старые записи по маркеру (чтобы не копились дубли)
sed -i "/$HOSTS_MARKER/d" /etc/hosts
# Добавляем актуальные
cat >> /etc/hosts <<_HOSTS
$HOSTS_MARKER
# Eltex corporate services (*.eltex.loc, mattermost, elph)
172.16.0.3 eltex.loc
172.16.5.103 intdocs.eltex.loc
172.16.5.251 red.eltex.loc
172.16.1.17 gitlab.eltex.loc
172.16.1.106 pixso.eltex.loc
172.16.1.94 mcpe-builder.eltex.loc
172.16.5.63 proxy.eltex.loc
10.80.0.16 ssw.eltex.loc
172.16.5.78 nexus.eltex.loc
172.16.1.149 cpe-worker.eltex.loc
172.16.5.22 mattermost.eltex-co.ru elph.eltex-co.ru ecss-elph-proxy.eltex-co.ru
_HOSTS
echo "Локальные хосты: *.eltex.loc, mattermost, elph → /etc/hosts"
# --- Обновляем RIPE-список (кэш 24ч) --- # --- Обновляем RIPE-список (кэш 24ч) ---
if [ ! -f "$CACHE" ] || [ $(( $(date +%s) - $(stat -c %Y "$CACHE" 2>/dev/null || echo 0) )) -gt 86400 ]; then if [ ! -f "$CACHE" ] || [ $(( $(date +%s) - $(stat -c %Y "$CACHE" 2>/dev/null || echo 0) )) -gt 86400 ]; then
@@ -201,7 +239,7 @@ if [ -n "${ALL_EXC// }" ]; then
if echo "$item" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$"; then if echo "$item" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$"; then
ips="$item" ips="$item"
else else
ips=$(dig +short "$item" A 2>/dev/null) ips=$(getent hosts "$item" 2>/dev/null | awk '{print $1}' | sort -u)
fi fi
for ip in $ips; do for ip in $ips; do
ipset add ru-direct "$ip" -exist 2>/dev/null || true ipset add ru-direct "$ip" -exist 2>/dev/null || true
@@ -255,7 +293,7 @@ if [ -n "${ALL_EXC// }" ]; then
if echo "$item" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$"; then if echo "$item" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$"; then
ips="$item" ips="$item"
else else
ips=$(dig +short "$item" A 2>/dev/null) ips=$(getent hosts "$item" 2>/dev/null | awk '{print $1}' | sort -u)
fi fi
for ip in $ips; do for ip in $ips; do
ip route replace "$ip/32" via "$GATEWAY" dev "$DEV" 2>/dev/null ip route replace "$ip/32" via "$GATEWAY" dev "$DEV" 2>/dev/null
@@ -279,34 +317,60 @@ if [ -n "$LOCAL_DNS" ]; then
fi fi
fi fi
# --- Правило в UFW before.rules (однократно, после создания ipset) --- # --- Правила в UFW before.rules (обновляются при каждом запуске) ---
# Маркеры используются для идентификации правил; DEV всегда актуальный.
UFW_MARKER="match-set $SETNAME" UFW_IPSET_MARKER="ru-bypass: ipset $SETNAME"
if ! grep -q "$UFW_MARKER" "$UFW_BEFORE" 2>/dev/null; then UFW_LOCAL_MARKER="ru-bypass: local-nets-bypass"
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" echo "Обновляем правила UFW before.rules..."
echo "UFW обновлён (.ru ipset)."
# Удаляем старые правила (если есть) — и в новом, и в старом формате маркеров
sed -i "/# $UFW_IPSET_MARKER/d; /# \.ru bypass (ipset $SETNAME)/d" "$UFW_BEFORE"
sed -i "/-A ufw-before-output -m set --match-set $SETNAME dst/d" "$UFW_BEFORE"
sed -i "/# $UFW_LOCAL_MARKER/d; /# local nets bypass (local-nets-bypass)/d" "$UFW_BEFORE"
sed -i "/-A ufw-before-output -d 10\.0\.0\.0\/8 -o/d" "$UFW_BEFORE"
sed -i "/-A ufw-before-output -d 172\.16\.0\.0\/12 -o/d" "$UFW_BEFORE"
sed -i "/-A ufw-before-output -d 192\.168\.0\.0\/16 -o/d" "$UFW_BEFORE"
# Добавляем правила заново с актуальным DEV
sed -i "0,/^COMMIT/{s/^COMMIT/# $UFW_IPSET_MARKER\n-A ufw-before-output -m set --match-set $SETNAME dst -o $DEV -j ACCEPT\nCOMMIT/}" "$UFW_BEFORE"
sed -i "0,/^COMMIT/{s/^COMMIT/# $UFW_LOCAL_MARKER\n-A ufw-before-output -d 10.0.0.0\/8 -o $DEV -j ACCEPT\n-A ufw-before-output -d 172.16.0.0\/12 -o $DEV -j ACCEPT\n-A ufw-before-output -d 192.168.0.0\/16 -o $DEV -j ACCEPT\nCOMMIT/}" "$UFW_BEFORE"
echo "UFW before.rules обновлён (ipset + локальные сети, DEV=$DEV)."
# --- Исправляем MANAGE_BUILTINS (должен быть yes, иначе before.rules не вызывается) ---
if grep -q '^MANAGE_BUILTINS=no' /etc/default/ufw 2>/dev/null; then
sed -i 's/^MANAGE_BUILTINS=no/MANAGE_BUILTINS=yes/' /etc/default/ufw
echo "UFW: MANAGE_BUILTINS исправлен (no → yes)."
fi fi
UFW_LOCAL_MARKER="local-nets-bypass"
if ! grep -q "$UFW_LOCAL_MARKER" "$UFW_BEFORE" 2>/dev/null; then
echo "Добавляем правила UFW для локальных сетей..."
sed -i "0,/^COMMIT/{s/^COMMIT/# local nets bypass ($UFW_LOCAL_MARKER)\n-A ufw-before-output -d 10.0.0.0\/8 -o $DEV -j ACCEPT\n-A ufw-before-output -d 172.16.0.0\/12 -o $DEV -j ACCEPT\n-A ufw-before-output -d 192.168.0.0\/16 -o $DEV -j ACCEPT\nCOMMIT/}" "$UFW_BEFORE"
echo "UFW обновлён (локальные сети)."
fi
# --- Настройка UFW default deny + allow amn0 (однократно) --- # --- Настройка UFW default deny + allow amn0 (однократно) ---
ufw default deny outgoing >/dev/null 2>&1 || true ufw default deny outgoing >/dev/null 2>&1 || true
ufw allow out on amn0 >/dev/null 2>&1 || true ufw allow out on amn0 >/dev/null 2>&1 || true
if grep -qE "$UFW_MARKER|$UFW_LOCAL_MARKER" "$UFW_BEFORE" 2>/dev/null; then if grep -qE "$UFW_IPSET_MARKER|$UFW_LOCAL_MARKER" "$UFW_BEFORE" 2>/dev/null; then
if ufw status | grep -qE "активен|active"; then if ufw status | grep -qE "активен|active"; then
ufw reload ufw reload
fi fi
fi fi
# --- Прямые правила iptables (гарантия работы даже при MANAGE_BUILTINS=no) ---
echo "Добавляем прямые правила iptables..."
# Правило для ipset ru-direct (RU-IP + исключения kill switch)
iptables -C OUTPUT -m set --match-set "$SETNAME" dst -o "$DEV" -j ACCEPT 2>/dev/null || \
iptables -I OUTPUT 1 -m set --match-set "$SETNAME" dst -o "$DEV" -j ACCEPT
# Правила для локальных сетей (RFC1918)
for _net in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16; do
iptables -C OUTPUT -d "$_net" -o "$DEV" -j ACCEPT 2>/dev/null || \
iptables -I OUTPUT 1 -d "$_net" -o "$DEV" -j ACCEPT
done
echo "iptables: прямые правила добавлены."
echo "" echo ""
echo "Готово." echo "Готово."
RU_EXAMPLE=$(dig +short ya.ru A 2>/dev/null | head -1) RU_EXAMPLE=$(dig +short ya.ru A 2>/dev/null | head -1)