get-ip-prefix/domain-routing.sh
2026-07-03 16:21:48 +03:00

304 lines
9.3 KiB
Bash
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
# Скрипт для domain-based routing под RHEL с firewalld, ipset, dnsmasq
# Направляет трафик определённых доменов через отдельный интерфейс/IP-набор
set -euo pipefail
# ============================================
# КОНФИГУРАЦИЯ
# ============================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/domain-routing.env"
# Загрузка конфигурации
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "❌ Ошибка: не найден конфигурационный файл $CONFIG_FILE"
echo "Создайте файл на основе domain-routing.env.example"
exit 1
fi
# shellcheck source=domain-routing.env
source "$CONFIG_FILE"
# ============================================
# ВАЛИДАЦИЯ ПЕРЕМЕННЫХ
# ============================================
required_vars=(
"DOMAINS_FILE"
"IPSET_NAME"
"FIREWALL_ZONE"
"DNSMASQ_CONF_DIR"
"RESOLV_CONF"
)
for var in "${required_vars[@]}"; do
[[ -z "${!var:-}" ]] && { echo "❌ Переменная $var не установлена в $CONFIG_FILE"; exit 1; }
done
# ============================================
# ФУНКЦИИ
# ============================================
log_info() {
echo " $1"
logger -t "domain-routing" "$1"
}
log_error() {
echo "$1"
logger -t "domain-routing" "ERROR: $1"
}
check_dependencies() {
log_info "Проверка зависимостей..."
local missing=()
for cmd in firewall-cmd ipset dnsmasq; do
if ! command -v "$cmd" &> /dev/null; then
missing+=("$cmd")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "Отсутствуют команды: ${missing[*]}"
exit 1
fi
log_info "Все зависимости установлены"
}
create_ipset() {
log_info "Создание ipset '$IPSET_NAME'..."
# Удаляем старый ipset если есть
if ipset list "$IPSET_NAME" &> /dev/null; then
ipset destroy "$IPSET_NAME"
log_info "Удалён старый ipset '$IPSET_NAME'"
fi
# Создаём новый ipset (hash:net для IP-сетей)
ipset create "$IPSET_NAME" hash:net hashsize 4096 family inet
log_info "Создан ipset '$IPSET_NAME'"
}
resolve_domains_to_ips() {
log_info "Разрешение доменов в IP-адреса..."
local temp_file=$(mktemp)
local resolved_count=0
while IFS= read -r domain || [[ -n "$domain" ]]; do
# Пропускаем комментарии и пустые строки
[[ -z "$domain" ]] && continue
[[ "$domain" =~ ^[[:space:]]*# ]] && continue
# Очищаем домен от лишних символов
domain=$(echo "$domain" | tr -d '[:space:]')
# Разрешаем домен в IP
local ips
ips=$(dig +short "$domain" A 2>/dev/null || true)
if [[ -n "$ips" ]]; then
echo "$ips" | while read -r ip; do
if [[ -n "$ip" ]]; then
echo "$ip" >> "$temp_file"
((resolved_count++)) || true
fi
done
else
log_error "Не удалось разрешить домен: $domain"
fi
done < "$DOMAINS_FILE"
# Добавляем IP в ipset
if [[ -s "$temp_file" ]]; then
while read -r ip; do
ipset add "$IPSET_NAME" "$ip"
done < "$temp_file"
log_info "Добавлено ${resolved_count} IP-адресов в ipset '$IPSET_NAME'"
else
log_error "Не удалось разрешить ни один домен"
rm -f "$temp_file"
exit 1
fi
rm -f "$temp_file"
}
configure_firewall() {
log_info "Настройка firewalld..."
# Проверяем зону
if ! firewall-cmd --query-zone="$FIREWALL_ZONE" &> /dev/null; then
log_info "Создание новой зоны '$FIREWALL_ZONE'..."
firewall-cmd --new-zone="$FIREWALL_ZONE" --permanent
fi
# Добавляем ipset в зону
firewall-cmd --zone="$FIREWALL_ZONE" --add-ipset="$IPSET_NAME" --permanent
# Перезагружаем firewalld
firewall-cmd --reload
log_info "Firewalld настроен для ipset '$IPSET_NAME' в зоне '$FIREWALL_ZONE'"
}
configure_dnsmasq() {
log_info "Настройка dnsmasq..."
# Создаём конфиг для dnsmasq
local dnsmasq_config="$DNSMASQ_CONF_DIR/domain-routing.conf"
mkdir -p "$DNSMASQ_CONF_DIR"
# Очищаем старый конфиг
echo "# Auto-generated domain routing config" > "$dnsmasq_config"
echo "# Generated: $(date)" >> "$dnsmasq_config"
# Добавляем правила для каждого домена
while IFS= read -r domain || [[ -n "$domain" ]]; do
[[ -z "$domain" ]] && continue
[[ "$domain" =~ ^[[:space:]]*# ]] && continue
domain=$(echo "$domain" | tr -d '[:space:]')
# Генерируем IP для каждого домена
local ips
ips=$(dig +short "$domain" A 2>/dev/null || true)
if [[ -n "$ips" ]]; then
echo "$ips" | while read -r ip; do
if [[ -n "$ip" ]]; then
echo "server=/$domain/$ip" >> "$dnsmasq_config"
fi
done
fi
done < "$DOMAINS_FILE"
log_info "Конфиг dnsmasq сгенерирован: $dnsmasq_config"
# Перезагружаем dnsmasq
if systemctl is-active --quiet dnsmasq; then
systemctl reload dnsmasq
log_info "Dnsmasq перезагружен"
else
log_info "Dnsmasq не запущен. Запустите: systemctl start dnsmasq"
fi
}
save_ipset() {
log_info "Сохранение ipset..."
local save_dir="/etc/ipset"
mkdir -p "$save_dir"
ipset save "$IPSET_NAME" -file "$save_dir/$IPSET_NAME.ipset"
log_info "Ipset сохранён в $save_dir/$IPSET_NAME.ipset"
}
restore_ipset_on_boot() {
log_info "Настройка автовосстановления ipset..."
# Создаём systemd service для восстановления ipset
local service_file="/etc/systemd/system/ipset-restore@$IPSET_NAME.service"
cat > "$service_file" << EOF
[Unit]
Description=Restore ipset $IPSET_NAME on boot
After=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ipset restore < /etc/ipset/$IPSET_NAME.ipset
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable "ipset-restore@$IPSET_NAME.service"
log_info "Systemd сервис создан и включён"
}
# ============================================
# ОСНОВНАЯ ЛОГИКА
# ============================================
main() {
echo "=============================================="
echo "Domain-based Routing Setup Script"
echo "=============================================="
# Проверка прав суперпользователя
if [[ $EUID -ne 0 ]]; then
log_error "Скрипт должен быть запущен от имени root"
exit 1
fi
# Опции командной строки
while [[ $# -gt 0 ]]; do
case $1 in
--domains-file)
DOMAINS_FILE="$2"
shift 2
;;
--ipset-name)
IPSET_NAME="$2"
shift 2
;;
--firewall-zone)
FIREWALL_ZONE="$2"
shift 2
;;
--dnsmasq-conf)
DNSMASQ_CONF_DIR="$2"
shift 2
;;
--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --domains-file FILE Файл со списком доменов"
echo " --ipset-name NAME Имя ipset (по умолчанию из config)"
echo " --firewall-zone ZONE Зона firewalld (по умолчанию из config)"
echo " --dnsmasq-conf DIR Директория конфигов dnsmasq"
echo " --help Показать эту справку"
exit 0
;;
*)
log_error "Неизвестный параметр: $1"
exit 1
;;
esac
done
echo ""
log_info "Конфигурация:"
echo " Domains file: $DOMAINS_FILE"
echo " IPset name: $IPSET_NAME"
echo " Firewall zone: $FIREWALL_ZONE"
echo " Dnsmasq conf: $DNSMASQ_CONF_DIR"
echo ""
# Шаги выполнения
check_dependencies
create_ipset
resolve_domains_to_ips
configure_firewall
configure_dnsmasq
save_ipset
restore_ipset_on_boot
echo ""
log_info "=========================================="
log_info "Domain-based routing успешно настроен!"
log_info "=========================================="
}
# Запуск основной функции
main "$@"