Commit 5e7e4541 authored by Vitaly Lipatov's avatar Vitaly Lipatov

router: decouple ip rule pref from table number, handle moved .list files

Pref was calculated as table_number × 10, meaning moving a .list between groups didn't change its priority. Now pref is assigned sequentially by group/list processing order (base=1000, group_step=100, list_step=10). Also improves cleanup_state to detect .list files moved between groups — only removes old state dir without flushing the shared routing table. Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 4def3d12
......@@ -16,7 +16,8 @@ ipcmd_for() { case "$1" in *routes6*) echo "ip -6" ;; *) echo "ip" ;; esac ; }
# Check if option is set in group's options file
has_option() { [ -f "$1/options" ] && grep -q "^$2$" "$1/options" 2>/dev/null ; }
# Get rule priority from table number (table × 10)
# DEPRECATED: pref is now assigned by group/list order in process_routes, not by table number.
# Kept for diagnostic use (e.g. checking old pref values).
rule_pref() { echo $(( $1 * 10 )) ; }
# Look up routing table number by name in /etc/iproute2/rt_tables
......
......@@ -17,6 +17,13 @@ ROUTES6_DIR=routes6.d
STATE_DIR=.state
HISTORY_SIZE=10
# Priority (pref) allocation for ip rules:
# Each group gets a PREF_GROUP_STEP-sized block, each .list gets PREF_STEP within.
# Groups are processed alphabetically; pref order matches group order.
PREF_BASE=1000
PREF_GROUP_STEP=100
PREF_STEP=10
BIRD_AS=65000
BIRD_CONF="/etc/bird/route-tables.conf"
......@@ -88,7 +95,9 @@ GATEWAY METRIC:
TABLE RESOLUTION:
Each .list file gets its own routing table (per-list tables).
Table number is auto-allocated in range 200-250 and registered in rt_tables.
ip rule pref = table_number × 10.
ip rule pref is assigned by group/list processing order (not by table number).
Groups are processed alphabetically; each group gets a 100-wide pref block
starting from 1000. Within a group, each .list gets pref += 10.
BIRD2 BGP INTEGRATION:
If BIRD2 is installed (birdc available), auto-generates
......@@ -334,6 +343,31 @@ fi
# _has_metric, _gw (first gateway IP), _f (list file path)
# Convention: _tag = "[group/list]" for log messages
# Fix ip rule pref for current list if it differs from expected _pref.
# Called when list is skipped (no reload) to keep prefs in sync with group order.
_fixup_rule_pref()
{
[ -n "$SHOW" ] && return 0
local _saved_pref=""
[ -f "$STATE_DIR/$_state/pref" ] && read -r _saved_pref < "$STATE_DIR/$_state/pref"
[ "$_saved_pref" = "$_pref" ] && return 0
# Pref changed — remove old rule(s) and add correct one
local _old_prefs=$($_ipcmd -N rule show | awk -v t="$_table" \
'/lookup/ { for(i=1;i<=NF;i++) if($i=="lookup" && $(i+1)==t) { for(j=1;j<=NF;j++) if($j=="pref" || $j=="priority") print $(j+1) } }')
for _op in $_old_prefs ; do
[ "$_op" = "$_pref" ] && continue
vlog "$_tag$_label pref changed: removing old rule pref $_op"
$_ipcmd rule del lookup "$_table" pref "$_op" 2>/dev/null
done
if ! $_ipcmd -N rule show | grep -q "lookup $_table" ; then
vlog "$_tag$_label pref changed: adding rule pref $_pref"
$_ipcmd rule add lookup "$_table" pref "$_pref" 2>/dev/null
fi
echo "$_pref" > "$STATE_DIR/$_state/pref"
log "$_tag$_label Updated ip rule pref: ${_saved_pref:-old}$_pref"
}
# Check if list needs processing (hash + rules/routes integrity)
# Sets: _current_hash, _need_reload, _need_resolve
# Returns 0 if needs processing, 1 if should skip
......@@ -367,6 +401,8 @@ check_list_changed()
if [ -n "$_need_reload" ] ; then
log "$_tag$_label Rules/routes lost, forcing reload"
else
# Fix ip rule pref if it changed (e.g. list moved between groups)
_fixup_rule_pref
vlog "$_tag$_label no changes, skipping"
return 1
fi
......@@ -541,8 +577,17 @@ load_list_routes()
vlog "$_tag$_label route count OK: $actual"
fi
# Ensure ip rule exists
if ! $_ipcmd -N rule show | grep -q "lookup $_table.*pref $_pref" ; then
# Ensure ip rule exists with correct pref
# Remove rules for this table with wrong pref (leftover from old pref scheme)
local _old_prefs=$($_ipcmd -N rule show | awk -v t="$_table" -v p="$_pref" \
'/lookup/ { for(i=1;i<=NF;i++) if($i=="lookup" && $(i+1)==t) { for(j=1;j<=NF;j++) if($j=="pref" || $j=="priority") { if($(j+1)!=p) print $(j+1) } } }')
if [ -n "$_old_prefs" ] ; then
for _op in $_old_prefs ; do
vlog "$_tag$_label removing stale ip rule: lookup $_table pref $_op"
$_ipcmd rule del lookup "$_table" pref "$_op" 2>/dev/null
done
fi
if ! $_ipcmd -N rule show | grep -q "lookup $_table" ; then
vlog "$_tag$_label adding ip rule: lookup $_table pref $_pref"
$_ipcmd rule add lookup "$_table" pref "$_pref" 2>/dev/null
fi
......@@ -551,6 +596,7 @@ load_list_routes()
echo "$_current_hash" > "$STATE_DIR/$_state/hash"
echo "$_list_hash" > "$STATE_DIR/$_state/list_hash"
echo "$_table" > "$STATE_DIR/$_state/table"
echo "$_pref" > "$STATE_DIR/$_state/pref"
cp "$_gwdir/gateway" "$STATE_DIR/$_state/gateway"
mv "$_resolved_new" "$STATE_DIR/$_state/resolved"
......@@ -568,6 +614,8 @@ process_routes()
[ -d "$routes_dir" ] || return 0
_pref_counter=$PREF_BASE
for _gwdir in "$routes_dir"/*/ ; do
[ -d "$_gwdir" ] || continue
local name=$(basename "$_gwdir")
......@@ -601,12 +649,16 @@ process_routes()
fi
# --- Per-list loop ---
# Align pref counter to next group boundary
_pref_counter=$(( (_pref_counter + PREF_GROUP_STEP - 1) / PREF_GROUP_STEP * PREF_GROUP_STEP ))
for _f in $lists ; do
local bname=$(basename "$_f")
local list_name="${bname%.list}"
_table=$(lookup_table "$list_name")
[ -z "$_table" ] && { log "[$name/$list_name]$_label Cannot allocate table" >&2 ; continue ; }
_pref=$(rule_pref "$_table")
_pref=$_pref_counter
_pref_counter=$((_pref_counter + PREF_STEP))
_state="$group_state/$list_name"
_tag="[$name/$list_name]"
......@@ -661,17 +713,24 @@ cleanup_state()
continue
fi
# Group exists — check for removed .list files
# Group exists — check for removed or moved .list files
for list_state in "$sdir"/*/ ; do
[ -d "$list_state" ] || continue
local lname=$(basename "$list_state")
if [ ! -f "$routes_dir/$name/${lname}.list" ] ; then
# Check if list moved to another group (don't flush shared table)
local moved_to=$(find -L "$routes_dir" -maxdepth 2 -name "${lname}.list" -type f 2>/dev/null | head -1)
local t=""
[ -f "$list_state/table" ] && read -r t < "$list_state/table"
log "Cleaning orphaned state for $name/$lname (table $t)"
if [ -z "$SHOW" ] && [ -n "$t" ] ; then
$ipcmd route flush table "$t" 2>/dev/null
$ipcmd rule del lookup "$t" 2>/dev/null
if [ -n "$moved_to" ] ; then
local new_group=$(basename "$(dirname "$moved_to")")
log "List $lname moved from $name to $new_group, removing old state (keeping table $t)"
else
log "Cleaning orphaned state for $name/$lname (table $t)"
if [ -z "$SHOW" ] && [ -n "$t" ] ; then
$ipcmd route flush table "$t" 2>/dev/null
$ipcmd rule del lookup "$t" 2>/dev/null
fi
fi
[ -z "$SHOW" ] && rm -rf "$list_state"
fi
......
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