345 lines
11 KiB
Bash
Executable file
345 lines
11 KiB
Bash
Executable file
#!/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 "$@"
|