Commit f98973c6 authored by Vitaly Lipatov's avatar Vitaly Lipatov

router: per-list tables, gateway metric, per-gateway failover

Per-list tables: each .list file gets its own routing table (auto-allocated 200-250) instead of one table per group. Enables BGP redistribution per list. Gateway metric: "IP metric N" syntax in gateway file. Multiple gateways with metric get separate route entries (preference-based) instead of ECMP multipath. Per-gateway failover: route-health.sh removes routes only via dead gateway in metric groups, keeping fallback routes alive. Refactored process_routes() into check_list_changed(), resolve_list_file(), load_list_routes() subfunctions for readability. Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 70b191be
...@@ -25,6 +25,16 @@ table_by_name() { awk -v n="$1" '$2 == n { print $1; exit }' /etc/iproute2/rt_ta ...@@ -25,6 +25,16 @@ table_by_name() { awk -v n="$1" '$2 == n { print $1; exit }' /etc/iproute2/rt_ta
# Resolve "default" keyword to actual default gateway # Resolve "default" keyword to actual default gateway
resolve_default_gw() { $1 route show default | awk '/default/ {print $3; exit}' ; } resolve_default_gw() { $1 route show default | awk '/default/ {print $3; exit}' ; }
# Parse gateway line: "IP [metric N]" → sets gw_ip, gw_metric
# Usage: parse_gw_line "91.232.225.122 metric 10" "ip"
parse_gw_line()
{
local line="$1" ipcmd="${2:-ip}"
local raw=$(echo "$line" | awk '{print $1}')
gw_ip=$(resolve_gw "$raw" "$ipcmd")
gw_metric=$(echo "$line" | awk '/metric/{for(i=1;i<=NF;i++) if($i=="metric") print $(i+1)}')
}
# Resolve gateway value: "default" → system default, hostname → IP, IP → as-is # Resolve gateway value: "default" → system default, hostname → IP, IP → as-is
resolve_gw() resolve_gw()
{ {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# Health-check failover for route groups via Telegraf/InfluxDB # Health-check failover for route groups via Telegraf/InfluxDB
# Monitors gateway health and flushes dead routes when live alternatives exist # Monitors gateway health and flushes dead routes when live alternatives exist
# Manages default route for groups with set-default option # Manages default route for groups with set-default option
# Supports per-list tables and per-gateway failover (metric mode)
# #
# Usage: route-health.sh [--show] # Usage: route-health.sh [--show]
...@@ -69,6 +70,51 @@ get_health() ...@@ -69,6 +70,51 @@ get_health()
}' }'
} }
# Find monitor tag for a gateway IP by scanning all groups
# Usage: find_gw_monitor ROUTES_DIR GW_IP IPCMD
find_gw_monitor()
{
local routes_dir="$1" gw_ip="$2" ipcmd="$3"
for check_dir in "$routes_dir"/*/ ; do
[ -d "$check_dir" ] || continue
[ -f "$check_dir/monitor" ] || continue
local check_ip=$(resolve_gw "$(read_value "$check_dir/gateway")" "$ipcmd" 2>/dev/null)
if [ "$check_ip" = "$gw_ip" ] ; then
read_value "$check_dir/monitor"
return
fi
done
}
# Flush per-list tables for a group
# Usage: flush_group_tables STATE_GROUP_DIR IPCMD
flush_group_tables()
{
local group_state="$1" ipcmd="$2"
for list_state in "$group_state"/*/ ; do
[ -d "$list_state" ] || continue
[ -f "$list_state/table" ] || continue
local t ; read -r t < "$list_state/table"
$ipcmd route flush table "$t" 2>/dev/null
done
}
# Remove routes via specific gateway from all per-list tables
# Usage: flush_gw_routes STATE_GROUP_DIR GW_IP IPCMD
flush_gw_routes()
{
local group_state="$1" dead_gw="$2" ipcmd="$3"
for list_state in "$group_state"/*/ ; do
[ -d "$list_state" ] || continue
[ -f "$list_state/table" ] || continue
local t ; read -r t < "$list_state/table"
# Remove routes via dead gateway
$ipcmd route show table "$t" 2>/dev/null | grep "via $dead_gw" | while read -r route ; do
$ipcmd route del $route table "$t" 2>/dev/null
done
done
}
# --- Evaluate health for all monitored groups --- # --- Evaluate health for all monitored groups ---
healthy_count=0 healthy_count=0
dead_count=0 dead_count=0
...@@ -129,24 +175,12 @@ for routes_dir in "$ROUTES_DIR" "$ROUTES6_DIR" ; do ...@@ -129,24 +175,12 @@ for routes_dir in "$ROUTES_DIR" "$ROUTES6_DIR" ; do
[ -z "$line" ] && continue [ -z "$line" ] && continue
echo "$line" | grep -q '^#' && continue echo "$line" | grep -q '^#' && continue
gw_ip=$(resolve_gw "$line" "$ipcmd") parse_gw_line "$line" "$ipcmd"
[ -z "$gw_ip" ] && continue [ -z "$gw_ip" ] && continue
# Find monitor tag for this gateway IP gw_tag=$(find_gw_monitor "$routes_dir" "$gw_ip" "$ipcmd")
# Look through all groups for one whose gateway matches
gw_tag=""
for check_dir in "$routes_dir"/*/ ; do
[ -d "$check_dir" ] || continue
[ -f "$check_dir/monitor" ] || continue
check_ip=$(resolve_gw "$(read_value "$check_dir/gateway")" "$ipcmd" 2>/dev/null)
if [ "$check_ip" = "$gw_ip" ] ; then
gw_tag=$(read_value "$check_dir/monitor")
break
fi
done
if [ -z "$gw_tag" ] ; then if [ -z "$gw_tag" ] ; then
# No monitor for this gateway — assume healthy (first unmonitored wins)
[ -n "$SHOW" ] && log "[$name] $gw_ip: no monitor, assuming healthy" [ -n "$SHOW" ] && log "[$name] $gw_ip: no monitor, assuming healthy"
best_gw="$gw_ip" best_gw="$gw_ip"
break break
...@@ -182,16 +216,11 @@ done ...@@ -182,16 +216,11 @@ done
for state_path in $dead_groups ; do for state_path in $dead_groups ; do
name=$(basename "$state_path") name=$(basename "$state_path")
routes_dir=$(dirname "$state_path") routes_dir=$(dirname "$state_path")
gwdir="$state_path"
mkdir -p "$STATE_DIR/$state_path" mkdir -p "$STATE_DIR/$state_path"
# Read table number from group config or rt_tables ipcmd=$(ipcmd_for "$routes_dir")
table=""
if [ -f "$state_path/table" ] ; then
table=$(read_value "$state_path/table")
else
table=$(table_by_name "$name")
fi
# Flapping protection: require DOWN_THRESHOLD consecutive checks # Flapping protection: require DOWN_THRESHOLD consecutive checks
down_count=0 down_count=0
...@@ -217,11 +246,44 @@ for state_path in $dead_groups ; do ...@@ -217,11 +246,44 @@ for state_path in $dead_groups ; do
continue continue
fi fi
ipcmd=$(ipcmd_for "$routes_dir") # Check if this is a metric group with per-gateway failover
has_metric=""
[ -f "$gwdir/gateway" ] && grep -v '^#' "$gwdir/gateway" | grep -q 'metric' && has_metric=1
if [ -n "$has_metric" ] ; then
# Metric mode: only remove routes via this dead gateway, keep others
# Find dead gateway IP from monitor tag
tag=$(read_value "$gwdir/monitor")
dead_gw=""
while IFS= read -r gw_line ; do
[ -z "$gw_line" ] && continue
echo "$gw_line" | grep -q '^#' && continue
parse_gw_line "$gw_line" "$ipcmd"
[ -z "$gw_ip" ] && continue
gw_tag=$(find_gw_monitor "$routes_dir" "$gw_ip" "$ipcmd")
if [ "$gw_tag" = "$tag" ] ; then
dead_gw="$gw_ip"
break
fi
done < "$gwdir/gateway"
if [ -n "$dead_gw" ] ; then
log "[$name] Dead (${down_count}x confirmed), removing routes via $dead_gw (metric failover)"
if [ -z "$SHOW" ] ; then
flush_gw_routes "$STATE_DIR/$state_path" "$dead_gw" "$ipcmd"
fi
else
log "[$name] Dead (${down_count}x confirmed), cannot identify dead gateway IP"
fi
else
# Non-metric: flush all per-list tables
log "[$name] Dead (${down_count}x confirmed), flushing all per-list tables"
if [ -z "$SHOW" ] ; then
flush_group_tables "$STATE_DIR/$state_path" "$ipcmd"
fi
fi
log "[$name] Dead (${down_count}x confirmed), flushing table $table" if [ -z "$SHOW" ] ; then
if [ -z "$SHOW" ] && [ -n "$table" ] ; then
$ipcmd route flush table "$table" 2>/dev/null
echo "down" > "$STATE_DIR/$state_path/health" echo "down" > "$STATE_DIR/$state_path/health"
echo "$down_count" > "$STATE_DIR/$state_path/down_count" echo "$down_count" > "$STATE_DIR/$state_path/down_count"
fi fi
...@@ -242,8 +304,11 @@ for state_path in $healthy_groups ; do ...@@ -242,8 +304,11 @@ for state_path in $healthy_groups ; do
log "[$name] Recovered, scheduling route reload" log "[$name] Recovered, scheduling route reload"
if [ -z "$SHOW" ] ; then if [ -z "$SHOW" ] ; then
echo "up" > "$STATE_DIR/$state_path/health" echo "up" > "$STATE_DIR/$state_path/health"
# Remove route-update state to trigger reload on next run # Remove per-list state hashes to trigger reload on next route-update run
rm -f "$STATE_DIR/$state_path/hash" "$STATE_DIR/$state_path/resolved" for list_state in "$STATE_DIR/$state_path"/*/ ; do
[ -d "$list_state" ] || continue
rm -f "$list_state/hash" "$list_state/resolved"
done
need_reload=1 need_reload=1
fi fi
done done
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment