#!/bin/bash VYOS_HOST="https://vyos-url" API_KEY="api-key" ASN_FILE="/opt/irr-update/asns.txt" LOG_FILE="/opt/irr-update/irr_updater.log" PEERINGDB_API_KEY="api-key" log() { local message="$1" echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" | tee -a "$LOG_FILE" } log "==========================================================" log "Starting IRR filter update process." log "==========================================================" declare -a current_entries while read -r entry; do if [[ ! "$entry" =~ ^# ]] && [[ -n "$entry" ]]; then current_entries+=("$entry") fi done < "$ASN_FILE" log "--- [PART 1] Processing IRR Prefix-Lists ---" log "Checking for orphaned prefix-lists..." has_orphaned_lists=false commands_list_orphans="" ipv4_lists_json=$(curl -s -k --location --request POST "${VYOS_HOST}/retrieve" \ --form data='{"op":"showConfig","path":["policy","prefix-list"]}' \ --form key="${API_KEY}" --max-time 300) ipv4_lists=$(echo "$ipv4_lists_json" | grep -o '"AS[0-9]*V4":' | sed 's/"://;s/"//g') for list_name in $ipv4_lists; do original_asn=$(echo "$list_name" | sed 's/V4$//;s/^AS//') is_orphaned=true for entry in "${current_entries[@]}"; do main_asn=$(echo "$entry" | cut -d':' -f1) [[ "$main_asn" == "$original_asn" ]] && is_orphaned=false done if [ "$is_orphaned" = true ]; then log "Found orphaned IPv4 prefix-list: $list_name. Adding delete command..." commands_list_orphans+='{"op":"delete","path":["policy","prefix-list","'"$list_name"'"]},' has_orphaned_lists=true fi done ipv6_lists_json=$(curl -s -k --location --request POST "${VYOS_HOST}/retrieve" \ --form data='{"op":"showConfig","path":["policy","prefix-list6"]}' \ --form key="${API_KEY}" --max-time 300) ipv6_lists=$(echo "$ipv6_lists_json" | grep -o '"AS[0-9]*V6":' | sed 's/"://;s/"//g') for list_name in $ipv6_lists; do original_asn=$(echo "$list_name" | sed 's/V6$//;s/^AS//') is_orphaned=true for entry in "${current_entries[@]}"; do main_asn=$(echo "$entry" | cut -d':' -f1) [[ "$main_asn" == "$original_asn" ]] && is_orphaned=false done if [ "$is_orphaned" = true ]; then log "Found orphaned IPv6 prefix-list: $list_name. Adding delete command..." commands_list_orphans+='{"op":"delete","path":["policy","prefix-list6","'"$list_name"'"]},' has_orphaned_lists=true fi done if [ "$has_orphaned_lists" = true ]; then commands_list_orphans="[${commands_list_orphans::-1}]" curl -s -k --location --request POST "${VYOS_HOST}/configure" \ --form data="${commands_list_orphans}" --form key="${API_KEY}" --max-time 300 > /dev/null log "Orphans deleted." else log "No orphaned prefix-lists found." fi for entry in "${current_entries[@]}"; do log "--------------------------------------------------------" log "Processing entry: $entry" main_asn=$(echo "$entry" | cut -d':' -f1) as_set=$(echo "$entry" | cut -s -d':' -f2-) declare -a asns_to_query=() asns_to_query+=("AS${main_asn}") if [ -n "$as_set" ]; then log "Resolving members of AS-Set: $as_set" resolved_asns=$(bgpq4 -j -t "$as_set" 2>/dev/null | jq -r '.[][]' | sed 's/^/AS/' | sort -u) for resolved_asn in $resolved_asns; do asns_to_query+=("$resolved_asn") done log "Resolved ASNs: ${asns_to_query[*]}" fi asns_to_query=($(echo "${asns_to_query[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) log "Unique ASNs to query: ${asns_to_query[*]}" declare -a ipv4_prefixes_to_add=() declare -a ipv6_prefixes_to_add=() declare -A seen_v4=() declare -A seen_v6=() for asn_to_query in "${asns_to_query[@]}"; do log "Fetching prefixes for $asn_to_query..." bgpq4_output_ipv6_raw=$(bgpq4 -6 -A "$asn_to_query" 2>&1) if ! echo "$bgpq4_output_ipv6_raw" | grep -q "FATAL ERROR"; then for prefix_with_length in $(echo "$bgpq4_output_ipv6_raw" | grep -oE '[0-9a-fA-F:]+/[0-9]+'); do [[ "$prefix_with_length" == "::/0" ]] && continue [[ -z "${seen_v6[$prefix_with_length]}" ]] && seen_v6[$prefix_with_length]=1 && ipv6_prefixes_to_add+=("$prefix_with_length") done fi bgpq4_output_ipv4_raw=$(bgpq4 -4 -A "$asn_to_query" 2>&1) if ! echo "$bgpq4_output_ipv4_raw" | grep -q "FATAL ERROR"; then for prefix_with_length in $(echo "$bgpq4_output_ipv4_raw" | grep -oE '[0-9.]+/[0-9]+'); do [[ "$prefix_with_length" == "0.0.0.0/0" ]] && continue [[ -z "${seen_v4[$prefix_with_length]}" ]] && seen_v4[$prefix_with_length]=1 && ipv4_prefixes_to_add+=("$prefix_with_length") done fi done commands_list_atomic="" pl_v4="AS${main_asn}V4" pl_v6="AS${main_asn}V6" local_has_changes=false commands_list_atomic+='{"op":"delete","path":["policy","prefix-list","'"$pl_v4"'"]},' commands_list_atomic+='{"op":"delete","path":["policy","prefix-list6","'"$pl_v6"'"]},' local_has_changes=true rule_id=10 for p in "${ipv4_prefixes_to_add[@]}"; do log "Adding IPv4 rule ${rule_id} for ${p}..." plen="${p#*/}" commands_list_atomic+='{"op":"set","path":["policy","prefix-list","'"$pl_v4"'","rule","'$rule_id'","action","permit"]},' commands_list_atomic+='{"op":"set","path":["policy","prefix-list","'"$pl_v4"'","rule","'$rule_id'","prefix","'$p'"]},' commands_list_atomic+='{"op":"set","path":["policy","prefix-list","'"$pl_v4"'","rule","'$rule_id'","le","24"]},' commands_list_atomic+='{"op":"set","path":["policy","prefix-list","'"$pl_v4"'","rule","'$rule_id'","ge","'$plen'"]},' rule_id=$((rule_id+10)) done rule_id=10 for p in "${ipv6_prefixes_to_add[@]}"; do log "Adding IPv6 rule ${rule_id} for ${p}..." plen="${p#*/}" commands_list_atomic+='{"op":"set","path":["policy","prefix-list6","'"$pl_v6"'","rule","'$rule_id'","action","permit"]},' commands_list_atomic+='{"op":"set","path":["policy","prefix-list6","'"$pl_v6"'","rule","'$rule_id'","prefix","'$p'"]},' commands_list_atomic+='{"op":"set","path":["policy","prefix-list6","'"$pl_v6"'","rule","'$rule_id'","le","48"]},' commands_list_atomic+='{"op":"set","path":["policy","prefix-list6","'"$pl_v6"'","rule","'$rule_id'","ge","'$plen'"]},' rule_id=$((rule_id+10)) done if [ "$local_has_changes" = true ]; then commands_list_atomic="[${commands_list_atomic::-1}]" log "Sending update for $entry..." api_response=$(curl -s -k --location --request POST "${VYOS_HOST}/configure" \ --form data="${commands_list_atomic}" \ --form key="${API_KEY}" --max-time 300) if echo "$api_response" | grep -q '"success": true'; then log "Update successful for $entry." else log "Update failed for $entry. Response: $api_response" fi fi done log "--- [PART 1] Completed. ---" log "--- [PART 2] Starting Max-Prefix updates (Source: PeeringDB) ---" log "Checking VRF 'bgp' configuration..." vrf_bgp_json=$(curl -s -k --location --request POST "${VYOS_HOST}/retrieve" \ --form data='{"op":"showConfig","path":["vrf","name","bgp","protocols","bgp"]}' \ --form key="${API_KEY}" --max-time 300) bgp_root=$(echo "$vrf_bgp_json" | jq -c '.data // .') active_asn=$(echo "$bgp_root" | jq -r 'if has("system-as") then .["system-as"] else (keys[] | select(test("^[0-9]+$"))) end' | head -n 1) if [ -z "$active_asn" ] || [ "$active_asn" == "null" ]; then log "ERROR: Could not find valid BGP configuration in VRF 'bgp'. Stopping Part 2." else log "Found BGP in VRF 'bgp' (ASN: $active_asn). Extracting neighbors..." neighbor_data=$(echo "$bgp_root" | jq -r --arg asn "$active_asn" ' if .neighbor then .neighbor else .[$asn].neighbor // empty end | to_entries[] | "\(.key)|\(.value["remote-as"])|\(.value["address-family"]["ipv4-unicast"]["maximum-prefix"] // "null")|\(.value["address-family"]["ipv6-unicast"]["maximum-prefix"] // "null")" ') if [ -z "$neighbor_data" ]; then log "No neighbors found in VRF 'bgp' configuration." fi commands_max_prefix="" has_max_changes=false has_system_as=$(echo "$bgp_root" | jq 'has("system-as")') if [ "$has_system_as" == "true" ]; then base_path='"vrf","name","bgp","protocols","bgp"' else base_path='"vrf","name","bgp","protocols","bgp","'"$active_asn"'"' fi while IFS='|' read -r neighbor_ip remote_as current_max_v4 current_max_v6; do if [ -z "$neighbor_ip" ]; then continue; fi if [ -z "$remote_as" ] || [ "$remote_as" == "null" ]; then continue; fi if [ "$remote_as" == "$active_asn" ]; then continue; fi log "Checking Neighbor: $neighbor_ip (AS$remote_as)" update_v4=false update_v6=false if [ "$current_max_v4" != "null" ]; then if [ "$current_max_v4" -gt 1 ]; then update_v4=true else log " > IPv4: Limit is $current_max_v4 (Protected). Skipping." fi else log " > IPv4: Limit is not set (Transit). Skipping." fi if [ "$current_max_v6" != "null" ]; then if [ "$current_max_v6" -gt 1 ]; then update_v6=true else log " > IPv6: Limit is $current_max_v6 (Protected). Skipping." fi else log " > IPv6: Limit is not set (Transit). Skipping." fi if [ "$update_v4" = false ] && [ "$update_v6" = false ]; then continue fi log " > Querying PeeringDB..." pdb_response=$(curl -s -G --header "Authorization: Api-Key $PEERINGDB_API_KEY" "https://www.peeringdb.com/api/net" --data-urlencode "asn=$remote_as") pdb_v4_count=$(echo "$pdb_response" | jq -r '.data[0].info_prefixes4 // 0') pdb_v6_count=$(echo "$pdb_response" | jq -r '.data[0].info_prefixes6 // 0') log " > PeeringDB Result: IPv4=$pdb_v4_count, IPv6=$pdb_v6_count" if [ "$update_v4" = true ]; then if [ "$pdb_v4_count" -gt 0 ]; then log " > IPv4: Updating limit $current_max_v4 -> $pdb_v4_count" commands_max_prefix+='{"op":"set","path":['"$base_path"',"neighbor","'"$neighbor_ip"'","address-family","ipv4-unicast","maximum-prefix","'"$pdb_v4_count"'"]},' has_max_changes=true else log " > IPv4: PeeringDB returned 0/error. Keeping $current_max_v4." fi fi if [ "$update_v6" = true ]; then if [ "$pdb_v6_count" -gt 0 ]; then log " > IPv6: Updating limit $current_max_v6 -> $pdb_v6_count" commands_max_prefix+='{"op":"set","path":['"$base_path"',"neighbor","'"$neighbor_ip"'","address-family","ipv6-unicast","maximum-prefix","'"$pdb_v6_count"'"]},' has_max_changes=true else log " > IPv6: PeeringDB returned 0/error. Keeping $current_max_v6." fi fi done <<< "$neighbor_data" if [ "$has_max_changes" = true ]; then commands_max_prefix="[${commands_max_prefix::-1}]" log "Sending Max-Prefix updates to API..." api_response=$(curl -s -k --location --request POST "${VYOS_HOST}/configure" \ --form data="${commands_max_prefix}" \ --form key="${API_KEY}" --max-time 300) echo "$api_response" | grep -q '"success": true' \ && log "Max-Prefix updates successful." \ || log "Max-Prefix updates failed: $api_response" else log "No max-prefix updates needed." fi fi log "--- [PART 2] Completed. ---" log "--------------------------------------------------------" log "Saving configuration..." curl -s -k --location --request POST "${VYOS_HOST}/config-file" \ --form data='{"op":"save"}' --form key="${API_KEY}" --max-time 300 > /dev/null log "==========================================================" log "Process finished." log "=========================================================="