Fortigate Multi-vdom BGP discovery and polling

Hello, community.
I was able to write scripts that discovers and poll BGP information from vdom enabled Fortigates. I need help/information how/where to share scripts so it can be useful for other members. Have in mind that I am pretty new to scripting and I suppose that it can be done better and integrated better with LibreNMS Here are some screenshots:

If the code is in our standard polling and discovery methods then create a pull request on GitHub. If this is completely bespoke then post here.

I have created discovery script in /opt/librenms/includes/discovery/bgp-peers/fortigate.inc.php, as in /opt/librenms/includes/discovery/bgp-peers.inc.php there is a os detection code.
for polling i have added to /opt/librenms/includes/polling/bgp-peers.inc.php os detection so I don`t make a mess and redirect execution in different file, which i believe it is not standard polling and discovery methods.

cat includes/discovery/bgp-peers/fortigate.inc.php
<?php

echo "Starting FortiGate BGP Discovery\n";

// Get VDOMs (Virtual Domains)
$vdoms = snmpwalk_cache_oid($device, 'fgVdEntName', [], 'FORTINET-FORTIGATE-MIB');
if (empty($vdoms)) {
   echo "No VDOMs detected. Exiting.\n";
   return;
}

// Iterate over each VDOM
foreach ($vdoms as $vd_index => $vd) {
   $vdom_name = $vd['fgVdEntName'];
   echo "Polling BGP for VDOM: $vdom_name (Index: $vd_index)\n";

   // Get BGP Local AS
   $bgp_local_as = snmp_get($device, "bgpLocalAs.$vd_index", '-Oqv', 'BGP4-MIB');
   if (!$bgp_local_as) {
       echo "No BGP Local AS found. Skipping VDOM.\n";
       continue;
   }
   echo "VDOM $vdom_name (Index: $vd_index) AS$bgp_local_as\n";

   // Get all required BGP SNMP values in one batch to optimize performance
   $bgp_data = [
       'bgpPeerRemoteAs'         => [],
       'bgpPeerState'            => [],
       'bgpPeerAdminStatus'      => [],
       'bgpPeerLocalAddr'        => [],
       'bgpPeerInUpdates'        => [],
       'bgpPeerOutUpdates'       => [],
       'bgpPeerInTotalMessages'  => [],
       'bgpPeerOutTotalMessages' => [],
       'bgpPeerFsmEstablishedTime' => [],
       'bgpPeerInUpdateElapsedTime' => []
   ];

   foreach (array_keys($bgp_data) as $oid) {
       $bgp_data[$oid] = snmpwalk_cache_oid($device, $oid, [], 'BGP4-MIB');
   }

   // Process each BGP peer
   foreach ($bgp_data['bgpPeerRemoteAs'] as $peer_oid => $peer) {
       $peer_remote_as = $peer['bgpPeerRemoteAs'];

       // **Remove trailing VDOM index from peer IP**
       $peer_ip_parts = explode('.', $peer_oid);
       array_pop($peer_ip_parts); // Remove last segment (VDOM index)
       $peer_ip = implode('.', $peer_ip_parts);

       // Gather additional values with defaults
       $peer_state = $bgp_data['bgpPeerState'][$peer_oid]['bgpPeerState'] ?? 0;
       $peer_admin = $bgp_data['bgpPeerAdminStatus'][$peer_oid]['bgpPeerAdminStatus'] ?? 0;
       $peer_local_addr = $bgp_data['bgpPeerLocalAddr'][$peer_oid]['bgpPeerLocalAddr'] ?? '0.0.0.0';
       $peer_in_updates = $bgp_data['bgpPeerInUpdates'][$peer_oid]['bgpPeerInUpdates'] ?? 0;
       $peer_out_updates = $bgp_data['bgpPeerOutUpdates'][$peer_oid]['bgpPeerOutUpdates'] ?? 0;
       $peer_in_messages = $bgp_data['bgpPeerInTotalMessages'][$peer_oid]['bgpPeerInTotalMessages'] ?? 0;
       $peer_out_messages = $bgp_data['bgpPeerOutTotalMessages'][$peer_oid]['bgpPeerOutTotalMessages'] ?? 0;
       $peer_fsm_time = $bgp_data['bgpPeerFsmEstablishedTime'][$peer_oid]['bgpPeerFsmEstablishedTime'] ?? 0;
       $peer_update_elapsed = $bgp_data['bgpPeerInUpdateElapsedTime'][$peer_oid]['bgpPeerInUpdateElapsedTime'] ?? 0;

       echo "Found peer $peer_ip (AS$peer_remote_as) in VDOM: $vdom_name\n";

       // Check if entry already exists using cleaned `peer_ip`
       $existing = dbFetchRow("SELECT bgpPeer_id FROM `bgpPeers` WHERE device_id = ? AND bgpPeerIdentifier = ? AND context_name = ?",
           [$device['device_id'], $peer_ip, $vdom_name]);

       $db_data = [
           'bgpPeerState'            => $peer_state,
           'bgpPeerAdminStatus'      => $peer_admin,
           'bgpLocalAddr'            => $peer_local_addr,
           'bgpPeerInUpdates'        => $peer_in_updates,
           'bgpPeerOutUpdates'       => $peer_out_updates,
           'bgpPeerInTotalMessages'  => $peer_in_messages,
           'bgpPeerOutTotalMessages' => $peer_out_messages,
           'bgpPeerFsmEstablishedTime' => $peer_fsm_time,
           'bgpPeerInUpdateElapsedTime' => $peer_update_elapsed
       ];

       if ($existing) {
           // Update existing entry
           dbUpdate($db_data, 'bgpPeers', 'device_id = ? AND bgpPeerIdentifier = ? AND context_name = ?',
               [$device['device_id'], $peer_ip, $vdom_name]);
       } else {
           // Insert new entry
           $db_data += [
               'device_id'        => $device['device_id'],
               'bgpPeerIdentifier' => $peer_ip,
               'bgpPeerRemoteAs'   => $peer_remote_as,
               'context_name'      => $vdom_name,
               'bgpPeerRemoteAddr' => $peer_ip
           ];
           dbInsert($db_data, 'bgpPeers');
       }
   }

   echo "Completed BGP discovery for VDOM: $vdom_name (Index: $vd_index)\n";
}

// Stop execution after FortiGate BGP discovery to prevent other BGP scripts from running
echo "BGP discovery for FortiGate (multi-VDOM) completed.\n";
exit;
/opt/librenms/includes/polling/bgp-peers.inc.php
<?php

use App\Models\Eventlog;
use Illuminate\Support\Str;
use LibreNMS\Enum\Severity;
use LibreNMS\Exceptions\InvalidIpException;
use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Util\IP;
use LibreNMS\Util\Oid;

if ($device['os'] == 'fortigate') {
    echo "Redirecting FortiGate BGP polling to fortigate-bgp-peers.inc.php\n";
    include __DIR__ . '/fortigate-bgp-peers.inc.php';
    return;
}
cat includes/polling/fortigate-bgp-peers.inc.php
<?php

d_echo("Polling FortiGate BGP peers...\n");

// Fetch all existing BGP peers from the database for this device
$existing_peers = dbFetchRows("SELECT bgpPeerIdentifier, context_name FROM `bgpPeers` WHERE device_id = ?", [$device['device_id']]);

if (empty($existing_peers)) {
    d_echo("No existing BGP peers found in the database for this device. Exiting polling.\n");
    return;
}

// Convert existing peers into a lookup array
$existing_peers_map = [];
foreach ($existing_peers as $peer) {
    $existing_peers_map[$peer['bgpPeerIdentifier']] = $peer['context_name'];
}

// Fetch VDOMs (Virtual Domains) from the device
$vdoms = snmpwalk_cache_oid($device, 'fgVdEntName', [], 'FORTINET-FORTIGATE-MIB');
if (empty($vdoms)) {
    d_echo("No VDOMs found. Skipping FortiGate BGP polling.\n");
    return;
}

// Iterate over each VDOM
foreach ($vdoms as $vd_index => $vd) {
    $vdom_name = $vd['fgVdEntName'];
    d_echo("Polling BGP for VDOM: $vdom_name (Index: $vd_index)\n");

    // Fetch BGP peers for this VDOM
    $bgp_peers = snmpwalk_cache_oid($device, 'bgpPeerRemoteAs', [], 'BGP4-MIB');
    if (empty($bgp_peers)) {
        d_echo("No BGP peers found via SNMP for VDOM: $vdom_name. Skipping.\n");
        continue;
    }

    // Fetch additional BGP attributes
    $bgp_state = snmpwalk_cache_oid($device, 'bgpPeerState', [], 'BGP4-MIB');
    $bgp_admin_status = snmpwalk_cache_oid($device, 'bgpPeerAdminStatus', [], 'BGP4-MIB');
    $bgp_local_addr = snmpwalk_cache_oid($device, 'bgpPeerLocalAddr', [], 'BGP4-MIB');
    $bgp_in_updates = snmpwalk_cache_oid($device, 'bgpPeerInUpdates', [], 'BGP4-MIB');
    $bgp_out_updates = snmpwalk_cache_oid($device, 'bgpPeerOutUpdates', [], 'BGP4-MIB');
    $bgp_in_messages = snmpwalk_cache_oid($device, 'bgpPeerInTotalMessages', [], 'BGP4-MIB');
    $bgp_out_messages = snmpwalk_cache_oid($device, 'bgpPeerOutTotalMessages', [], 'BGP4-MIB');
    $bgp_fsm_time = snmpwalk_cache_oid($device, 'bgpPeerFsmEstablishedTime', [], 'BGP4-MIB');
    $bgp_update_elapsed = snmpwalk_cache_oid($device, 'bgpPeerInUpdateElapsedTime', [], 'BGP4-MIB');

    // Process each peer and update only if it exists in the database
    foreach ($bgp_peers as $peer_index => $peer) {
        $peer_ip_full = $peer_index; // Example: "169.254.59.37.1"

        // Remove last octet if it's a single number (VDOM index)
        $peer_ip_parts = explode('.', $peer_ip_full);
        if (count($peer_ip_parts) > 4) {
            array_pop($peer_ip_parts); // Remove VDOM index
        }
        $peer_ip = implode('.', $peer_ip_parts); // Corrected peer IP

        // Check if the peer exists in the database
        if (!isset($existing_peers_map[$peer_ip])) {
            d_echo("Skipping unknown BGP peer: $peer_ip (Not found in database)\n");
            continue;
        }

        // Fetch additional SNMP values
        $peer_state = $bgp_state[$peer_index]['bgpPeerState'] ?? 'idle';
        $peer_admin = $bgp_admin_status[$peer_index]['bgpPeerAdminStatus'] ?? 'stop';
        $peer_local_addr = $bgp_local_addr[$peer_index]['bgpPeerLocalAddr'] ?? '0.0.0.0';
        $peer_in_updates = $bgp_in_updates[$peer_index]['bgpPeerInUpdates'] ?? 0;
        $peer_out_updates = $bgp_out_updates[$peer_index]['bgpPeerOutUpdates'] ?? 0;
        $peer_in_messages = $bgp_in_messages[$peer_index]['bgpPeerInTotalMessages'] ?? 0;
        $peer_out_messages = $bgp_out_messages[$peer_index]['bgpPeerOutTotalMessages'] ?? 0;
        $peer_fsm_time = $bgp_fsm_time[$peer_index]['bgpPeerFsmEstablishedTime'] ?? 0;
        $peer_update_elapsed = $bgp_update_elapsed[$peer_index]['bgpPeerInUpdateElapsedTime'] ?? 0;

        d_echo("Updating BGP peer: $peer_ip (AS{$peer['bgpPeerRemoteAs']}) in VDOM: $vdom_name\n");

        // Update the database for this peer
        dbUpdate([
            'bgpPeerState'            => $peer_state,
            'bgpPeerAdminStatus'      => $peer_admin,
            'bgpLocalAddr'            => $peer_local_addr,
            'bgpPeerInUpdates'        => $peer_in_updates,
            'bgpPeerOutUpdates'       => $peer_out_updates,
            'bgpPeerInTotalMessages'  => $peer_in_messages,
            'bgpPeerOutTotalMessages' => $peer_out_messages,
            'bgpPeerFsmEstablishedTime' => $peer_fsm_time,
            'bgpPeerInUpdateElapsedTime' => $peer_update_elapsed,
        ], 'bgpPeers', 'device_id = ? AND bgpPeerIdentifier = ?', [
            $device['device_id'],
            $peer_ip
        ]);
        d_echo("Updated BGP peer: $peer_ip in VDOM: $vdom_name\n");
    }
}

d_echo("Completed FortiGate BGP polling.\n");
?>