#!/bin/bash # Скрипт для domain-based routing под RHEL с BIRD BGP # Направляет трафик определённых доменов через отдельный маршрут в BIRD # С поддержкой суммаризации маршрутов до /24 set -euo pipefail # ============================================ # КОНФИГУРАЦИЯ # ============================================ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="$SCRIPT_DIR/domain-routing-bird.env" # Загрузка конфигурации if [[ ! -f "$CONFIG_FILE" ]]; then echo "❌ Ошибка: не найден конфигурационный файл $CONFIG_FILE" echo "Создайте файл на основе domain-routing-bird.env.example" exit 1 fi # shellcheck source=domain-routing-bird.env source "$CONFIG_FILE" # ============================================ # ВАЛИДАЦИЯ ПЕРЕМЕННЫХ # ============================================ required_vars=( "DOMAINS_FILE" "BIRD_CONF_DIR" "BIRD_CONF_FILE" "ROUTE_GATEWAY" "ROUTE_INTERFACE" ) for var in "${required_vars[@]}"; do [[ -z "${!var:-}" ]] && { echo "❌ Переменная $var не установлена в $CONFIG_FILE"; exit 1; } done # ============================================ # ФУНКЦИИ # ============================================ log_info() { echo "ℹ️ $1" logger -t "domain-routing-bird" "$1" } log_error() { echo "❌ $1" logger -t "domain-routing-bird" "ERROR: $1" } check_dependencies() { log_info "Проверка зависимостей..." local missing=() for cmd in birdc dig; 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 "Все зависимости установлены" } resolve_domains_to_ips() { log_info "Разрешение доменов в IP-адреса..." local temp_file=$(mktemp) local ipv4_file=$(mktemp) local ipv6_file=$(mktemp) while IFS= read -r domain || [[ -n "$domain" ]]; do # Пропускаем комментарии и пустые строки [[ -z "$domain" ]] && continue [[ "$domain" =~ ^[[:space:]]*# ]] && continue # Очищаем домен от лишних символов domain=$(echo "$domain" | tr -d '[:space:]') # Разрешаем домен в IPv4 if [[ "${ENABLE_IPV4:-true}" == "true" ]]; then local ipv4 ipv4=$(dig +short "$domain" A 2>/dev/null || true) if [[ -n "$ipv4" ]]; then echo "$ipv4" >> "$ipv4_file" fi fi # Разрешаем домен в IPv6 if [[ "${ENABLE_IPV6:-false}" == "true" ]]; then local ipv6 ipv6=$(dig +short "$domain" AAAA 2>/dev/null || true) if [[ -n "$ipv6" ]]; then echo "$ipv6" >> "$ipv6_file" fi fi done < "$DOMAINS_FILE" # Объединяем результаты if [[ "${ENABLE_IPV4:-true}" == "true" ]] && [[ -s "$ipv4_file" ]]; then cat "$ipv4_file" >> "$temp_file" fi if [[ "${ENABLE_IPV6:-false}" == "true" ]] && [[ -s "$ipv6_file" ]]; then cat "$ipv6_file" >> "$temp_file" fi # Удаляем дубликаты local unique_file=$(mktemp) sort -u "$temp_file" > "$unique_file" local count=$(wc -l < "$unique_file") log_info "Разрешено $count уникальных IP-адресов" rm -f "$temp_file" "$ipv4_file" "$ipv6_file" echo "$unique_file" } # Функция для суммаризации IPv4 маршрутов по маске (до /24) aggregate_routes_ipv4() { local ip_file="$1" local output_file="$2" log_info "Суммаризация IPv4 маршрутов до /24..." # Создаём временный файл для сетей local network_file=$(mktemp) # Преобразуем IP в сети /24 while read -r ip; do if [[ -n "$ip" ]] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then # Получаем сеть /24 для IP local network=$(echo "$ip" | cut -d. -f1-3) echo "${network}.0/24" fi done < "$ip_file" | sort -u > "$network_file" local total_networks=$(wc -l < "$network_file") log_info "Создано $total_networks сетей /24" # Копируем в выходной файл cp "$network_file" "$output_file" local aggregated_count=$(wc -l < "$output_file") log_info "После суммаризации: $aggregated_count сетей" rm -f "$network_file" } generate_bird_config() { local ip_file="$1" local output_file="$BIRD_CONF_DIR/$BIRD_CONF_FILE" log_info "Генерация конфига BIRD: $output_file" mkdir -p "$BIRD_CONF_DIR" # Создаём заголовок echo "# Auto-generated domain routing config" > "$output_file" echo "# Generated: $(date)" >> "$output_file" echo "# Source: $DOMAINS_FILE" >> "$output_file" echo "" >> "$output_file" # Если включена суммаризация IPv4, используем агрегированные сети if [[ "${ENABLE_IPV4:-true}" == "true" ]] && [[ "${AGGREGATE_TO_24:-true}" == "true" ]]; then local aggregated_file=$(mktemp) aggregate_routes_ipv4 "$ip_file" "$aggregated_file" # Генерируем route statements для суммаризированных IPv4 сетей while read -r network; do if [[ -n "$network" ]]; then echo "route $network via $ROUTE_GATEWAY on $ROUTE_INTERFACE;" >> "$output_file" fi done < "$aggregated_file" rm -f "$aggregated_file" else # Без суммаризации - добавляем отдельные IP while read -r ip; do if [[ -n "$ip" ]]; then # Определяем тип IP (IPv4 или IPv6) if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if [[ "${ENABLE_IPV4:-true}" == "true" ]]; then if [[ "${AGGREGATE_TO_24:-true}" == "true" ]]; then # Конвертируем в /24 сеть local network=$(echo "$ip" | cut -d. -f1-3) echo "route ${network}.0/24 via $ROUTE_GATEWAY on $ROUTE_INTERFACE;" >> "$output_file" else echo "route $ip via $ROUTE_GATEWAY on $ROUTE_INTERFACE;" >> "$output_file" fi fi elif [[ "$ip" =~ : ]]; then if [[ "${ENABLE_IPV6:-false}" == "true" ]]; then echo "route $ip via $ROUTE_GATEWAY on $ROUTE_INTERFACE;" >> "$output_file" fi fi fi done < "$ip_file" fi log_info "Конфиг BIRD сгенерирован: $output_file" } reload_bird() { log_info "Перезагрузка BIRD..." if /usr/sbin/birdc configure; then logger "BIRD domain routing reconfigured" log_info "BIRD успешно перезагружен" else log_error "Ошибка при перезагрузке BIRD" exit 1 fi } save_config_hash() { local output_file="$BIRD_CONF_DIR/$BIRD_CONF_FILE" local hash_file="$BIRD_CONF_DIR/.${BIRD_CONF_FILE}.md5" if [[ -f "$output_file" ]]; then md5sum "$output_file" | cut -c1-32 > "$hash_file" log_info "MD5 хеш сохранён: $hash_file" fi } restore_config_on_boot() { log_info "Настройка автовосстановления конфига BIRD..." # Создаём systemd service для перезагрузки BIRD local service_file="/etc/systemd/system/bird-domain-routing.service" cat > "$service_file" << EOF [Unit] Description=BIRD Domain Routing Configuration After=network.target Wants=network.target [Service] Type=oneshot ExecStart=/usr/sbin/birdc configure RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF systemctl enable bird-domain-routing.service log_info "Systemd сервис создан и включён: bird-domain-routing.service" } # ============================================ # ОСНОВНАЯ ЛОГИКА # ============================================ main() { echo "==============================================" echo "Domain-based Routing for BIRD BGP" 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 ;; --bird-conf-dir) BIRD_CONF_DIR="$2" shift 2 ;; --bird-conf-file) BIRD_CONF_FILE="$2" shift 2 ;; --gateway) ROUTE_GATEWAY="$2" shift 2 ;; --interface) ROUTE_INTERFACE="$2" shift 2 ;; --help) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --domains-file FILE Файл со списком доменов" echo " --bird-conf-dir DIR Директория конфигов BIRD" echo " --bird-conf-file FILE Имя файла конфига" echo " --gateway GATEWAY Шлюз для маршрутов" echo " --interface INTERFACE Интерфейс для маршрутов" echo " --help Показать эту справку" exit 0 ;; *) log_error "Неизвестный параметр: $1" exit 1 ;; esac done echo "" log_info "Конфигурация:" echo " Domains file: $DOMAINS_FILE" echo " BIRD conf dir: $BIRD_CONF_DIR" echo " BIRD conf file: $BIRD_CONF_FILE" echo " Route gateway: $ROUTE_GATEWAY" echo " Route interface: $ROUTE_INTERFACE" echo " IPv4 enabled: ${ENABLE_IPV4:-true}" echo " IPv6 enabled: ${ENABLE_IPV6:-false}" echo " Aggregate to /24: ${AGGREGATE_TO_24:-true}" echo "" # Шаги выполнения check_dependencies local ip_file ip_file=$(resolve_domains_to_ips) generate_bird_config "$ip_file" reload_bird save_config_hash restore_config_on_boot echo "" log_info "==========================================" log_info "Domain-based routing для BIRD настроен!" log_info "==========================================" } # Запуск основной функции main "$@"