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

345 lines
11 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 с 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 "$@"