BGP Peer Device Links Incorrect When IP Address Space Is Reused Across Devices

Hey community,

In environments where the same IP address range is reused across many devices (common in MSP/ISP deployments using /30 or /31 BGP peering subnets), LibreNMS incorrectly links BGP peers to unrelated devices in the UI. The BGP Routing tab on a device shows hostnames and interface links for devices that are not actual BGP peers.

Environment

  • LibreNMS version: current master

  • Affected file: includes/polling/bgp-peers.inc.php

  • Affected UI: Device → Routing → BGP (peer address column shows wrong device hostname/link)

Expected Behaviour

bgpPeerIface should only be populated when the peer IP is found on a port belonging to the same device that owns the BGP session, or on a directly connected device that can be confirmed as the actual peer. In environments with reused address space, no device link should be shown rather than an incorrect one.

Actual Behaviour

The peer IP lookup query has no device_id scope — it matches any device in the database that has that IP assigned to a port:

// includes/polling/bgp-peers.inc.php
$peer_data[‘bgpPeerIface’] = DB::table(‘ports’)
->join(“{$family}_addresses”, ‘ports.port_id’, ‘=’, “{$family}_addresses.port_id”)
->where(“{$family}_address”, ‘=’, $ip_address->uncompressed())
->value(‘ifIndex’);

Impact

  • Cosmetic but misleading: NOC operators following a BGP peer link are taken to a completely unrelated customer’s device.

  • Affects all devices with BGP peers in reused address space.

  • Re-populated on every poll cycle so a one-time DB fix is not persistent.

  • No current config option exists to disable this behaviour.

Root Cause

The bgpPeerIface population logic in includes/polling/bgp-peers.inc.php performs a global IP-to-port lookup without constraining the search to the device being polled or its directly connected neighbours:

// Current code
$peer_data[‘bgpPeerIface’] = DB::table(‘ports’)
->join(“{$family}_addresses”, ‘ports.port_id’, ‘=’, “{$family}_addresses.port_id”)
->where(“{$family}_address”, ‘=’, $ip_address->uncompressed())
->value(‘ifIndex’);

This was introduced as part of PR #13785 (BGP unnumbered support) to resolve peer IPs to local interface names. The intent was to link BGP peers to the interface on the local device through which the session runs, but the query inadvertently matches any device fleet-wide.

Proposed Fix

Add a device_id constraint to the lookup so it only resolves to ports on the device currently being polled:

// Proposed fix - add in a constrain to current device
$peer_data[‘bgpPeerIface’] = DB::table(‘ports’)
->join(“{$family}_addresses”, ‘ports.port_id’, ‘=’, “{$family}_addresses.port_id”)
->where(“{$family}_address”, ‘=’, $ip_address->uncompressed())
->where(‘ports.device_id’, ‘=’, $device[‘device_id’])
->value(‘ifIndex’);

This ensures that the peer IP is only resolved to an interface if that interface actually belongs to the device being polled, which is the only case where the link is meaningful. If the peer IP is not found on the local device (the normal case for external BGP peers), bgpPeerIface remains null and no incorrect device link is displayed.

Until a fix is available, a cron job can be used to periodically clear the incorrect links:
/etc/cron.d/librenms-bgp-peerfix

*/30 * * * * librenms mysql librenms -e “UPDATE bgpPeers SET bgpPeerIface = NULL;” > /dev/null 2>&1

This has no effect on BGP session monitoring or alerting - bgpPeerIface is used only for the UI device link display.

Might be a silly question, but what is the best way to get the proposed fix checked over and added as my “workaround” is not ideal and short of patching it myself and having it written over at each pull.