#!/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 "$@"