mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Add BIRD2 BGP Peers application support (#14466)
* Add BIRD2 application support and docs * Remove whitespacing * Update includes/polling/applications/bird2.inc.php Co-authored-by: Tony Murray <murraytony@gmail.com> * Correct the variable names to reflect their actual content and also use the remote address to display on the webUI as opposed to the unrelated peerid aka router-id * Style fixes * Style fixes --------- Co-authored-by: Tony Murray <murraytony@gmail.com>
This commit is contained in:
@@ -388,6 +388,30 @@ chmod +x /usr/lib/check_mk_agent/local/bind
|
||||
|
||||
3. Set the variable 'agent' to '1' in the config.
|
||||
|
||||
## BIRD2
|
||||
|
||||
The BIRD Internet Routing Daemon (BGP)
|
||||
|
||||
Due to the lack of SNMP support in the BIRD daemon, this application extracts all configured BGP protocols and parses it into LibreNMS.
|
||||
This application supports both IPv4 and IPv6 Peer processing.
|
||||
|
||||
### SNMP Extend
|
||||
1. Edit your snmpd.conf file (usually /etc/snmp/snmpd.conf) and add:
|
||||
```
|
||||
extend bird2 '/usr/bin/sudo /usr/sbin/birdc -r show protocols all'
|
||||
```
|
||||
|
||||
2. Edit your sudo users (usually `visudo`) and add at the bottom:
|
||||
|
||||
```
|
||||
Debian-snmp ALL=(ALL) NOPASSWD: /usr/sbin/birdc
|
||||
```
|
||||
_If your snmp daemon is running on a user that isnt `Debian-snmp` make sure that user has the correct permission to execute `birdc`_
|
||||
3. Restart snmpd on your host
|
||||
|
||||
The application should be auto-discovered as described at the top of the page. If it is not, please follow the steps set out under `SNMP Extend` heading top of page.
|
||||
|
||||
|
||||
## Certificate
|
||||
|
||||
A small python3 script that checks age and remaining validity of certificates
|
||||
|
@@ -255,13 +255,13 @@ if (! Auth::user()->hasGlobalRead()) {
|
||||
}
|
||||
|
||||
try {
|
||||
$peer_ip = new IPv6($peer['bgpLocalAddr']);
|
||||
$local_addr = new IPv6($peer['bgpLocalAddr']);
|
||||
} catch (InvalidIpException $e) {
|
||||
$peer_ip = $peer['bgpLocalAddr'];
|
||||
$local_addr = $peer['bgpLocalAddr'];
|
||||
}
|
||||
|
||||
try {
|
||||
$peer_ident = new IPv6($peer['bgpPeerIdentifier']);
|
||||
$peer_addr = new IPv6($peer['bgpPeerRemoteAddr'] != '0.0.0.0' ? $peer['bgpPeerRemoteAddr'] : $peer['bgpPeerIdentifier']);
|
||||
} catch (InvalidIpException $e) {
|
||||
$peer_ident = $peer['bgpPeerIdentifier'];
|
||||
}
|
||||
@@ -280,7 +280,7 @@ if (! Auth::user()->hasGlobalRead()) {
|
||||
$graph_array_zoom['height'] = '150';
|
||||
$graph_array_zoom['width'] = '500';
|
||||
$overlib_link = 'device/device=' . $peer['device_id'] . '/tab=routing/proto=bgp/';
|
||||
$peeraddresslink = '<span class=list-large>' . \LibreNMS\Util\Url::overlibLink($overlib_link, $peer_ident, \LibreNMS\Util\Url::graphTag($graph_array_zoom)) . '</span>';
|
||||
$peeraddresslink = '<span class=list-large>' . \LibreNMS\Util\Url::overlibLink($overlib_link, $peer_addr, \LibreNMS\Util\Url::graphTag($graph_array_zoom)) . '</span>';
|
||||
|
||||
// Local Address
|
||||
$graph_array['afi'] = 'ipv4';
|
||||
@@ -288,7 +288,7 @@ if (! Auth::user()->hasGlobalRead()) {
|
||||
$graph_array_zoom['afi'] = 'ipv4';
|
||||
$graph_array_zoom['safi'] = 'unicast';
|
||||
$overlib_link = 'device/device=' . $peer['device_id'] . '/tab=routing/proto=bgp/';
|
||||
$localaddresslink = '<span class=list-large>' . \LibreNMS\Util\Url::overlibLink($overlib_link, $peer_ip, \LibreNMS\Util\Url::graphTag($graph_array_zoom)) . '</span>';
|
||||
$localaddresslink = '<span class=list-large>' . \LibreNMS\Util\Url::overlibLink($overlib_link, $local_addr, \LibreNMS\Util\Url::graphTag($graph_array_zoom)) . '</span>';
|
||||
|
||||
if ($peer['bgpPeerLastErrorCode'] == 0 && $peer['bgpPeerLastErrorSubCode'] == 0) {
|
||||
$last_error = $peer['bgpPeerLastErrorText'];
|
||||
|
194
includes/polling/applications/bird2.inc.php
Normal file
194
includes/polling/applications/bird2.inc.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
use App\Models\BgpPeer;
|
||||
use App\Models\Device;
|
||||
use Carbon\Carbon;
|
||||
|
||||
$name = 'bird2';
|
||||
|
||||
if (! \LibreNMS\Config::get('enable_bgp')) {
|
||||
echo PHP_EOL . $name . ': BGP is not enabled in config' . PHP_EOL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$birdOutput = snmp_get($device, 'nsExtendOutputFull.' . string_to_oid($name), '-Oqv', 'NET-SNMP-EXTEND-MIB');
|
||||
|
||||
// make sure we actually get something back
|
||||
if (empty($birdOutput)) {
|
||||
echo PHP_EOL . $name . ': has empty output' . PHP_EOL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ========
|
||||
// Process the actual BIRD2 output
|
||||
$protocolsData = [];
|
||||
|
||||
// Remove headers
|
||||
$birdOutput = trim(explode('Name Proto Table State Since Info', $birdOutput, 2)[1]);
|
||||
$protocolSegments = explode("\n\n", $birdOutput);
|
||||
|
||||
// Remove the first title
|
||||
unset($protocolSegments[0]);
|
||||
|
||||
foreach ($protocolSegments as $protocolSegment) {
|
||||
// Deal with the title first
|
||||
$protocolSegmentParts = explode("\n", $protocolSegment, 2);
|
||||
$titleParts = preg_split("/\s+/", $protocolSegmentParts[0], 5);
|
||||
|
||||
// make sure we only look at BGP protocols
|
||||
if ($titleParts[1] !== 'BGP') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$protocolData = [
|
||||
'name' => $titleParts[0],
|
||||
'type' => $titleParts[1],
|
||||
'table' => $titleParts[2],
|
||||
'protocol_state' => $titleParts[3],
|
||||
'since' => preg_split("/\s+/", $titleParts[4], 3)[0] . ' ' . preg_split("/\s+/", $titleParts[4], 3)[1],
|
||||
];
|
||||
|
||||
// Deal with the rest of the body
|
||||
$protocolBodys = preg_split("/^\s{2}([A-Z])/m", $protocolSegmentParts[1]);
|
||||
|
||||
// Loop through all BGP protocols
|
||||
foreach ($protocolBodys as $protocolBody) {
|
||||
// Deal with the BGP block
|
||||
if (strpos($protocolBody, 'GP') === 0) {
|
||||
foreach (explode("\n", 'B' . $protocolBody) as $protocolBodyLine) {
|
||||
if (strpos($protocolBodyLine, ':') !== false) {
|
||||
$lineParts = explode(':', $protocolBodyLine, 2);
|
||||
$protocolData[str_replace(' ', '_', strtolower(trim($lineParts[0])))] = trim($lineParts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fix up the error string
|
||||
if (isset($protocolData['last_error'])) {
|
||||
// Trim the received
|
||||
$protocolData['last_error'] = trim(str_ireplace('Received:', '', $protocolData['last_error']));
|
||||
}
|
||||
}
|
||||
|
||||
// Process the Ip channel (v4/v6)
|
||||
$IpVersion = 4;
|
||||
if (filter_var($protocolData['neighbor_address'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$IpVersion = 6;
|
||||
}
|
||||
|
||||
if (strpos($protocolBody, 'hannel ipv' . $IpVersion) === 0) {
|
||||
foreach (explode("\n", 'C' . $protocolBody) as $protocolBodyLine) {
|
||||
if (strpos($protocolBodyLine, ':') !== false) {
|
||||
$lineParts = explode(':', $protocolBodyLine, 2);
|
||||
$protocolData[str_replace(' ', '_', strtolower(trim($lineParts[0])))] = trim($lineParts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fix up the ROUTES
|
||||
if (isset($protocolData['routes'])) {
|
||||
$routeParts = explode(', ', $protocolData['routes']);
|
||||
unset($protocolData['routes']);
|
||||
foreach ($routeParts as $routePart) {
|
||||
$routeDetail = explode(' ', $routePart);
|
||||
$protocolData['routes'][$routeDetail[1]] = $routeDetail[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Set the route updates
|
||||
unset($protocolData['route_change_stats']);
|
||||
foreach (['import_updates', 'import_withdraws', 'export_updates', 'export_withdraws'] as $key) {
|
||||
if (! isset($protocolData[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$routeChange_parts = preg_split("/\s+/", trim($protocolData[$key]));
|
||||
|
||||
unset($protocolData[$key]);
|
||||
$protocolData['route_change_stats'][$key] = [
|
||||
'received' => $routeChange_parts[0],
|
||||
'rejected' => $routeChange_parts[1],
|
||||
'filtered' => $routeChange_parts[2],
|
||||
'ignored' => $routeChange_parts[3],
|
||||
'accepted' => $routeChange_parts[4],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$protocolsData[] = $protocolData;
|
||||
}
|
||||
|
||||
// ---
|
||||
$deviceObj = DeviceCache::getPrimary();
|
||||
|
||||
if (empty($protocolsData)) {
|
||||
echo PHP_EOL . $name . ': No BGP Peers found' . PHP_EOL;
|
||||
$deviceObj->bgpLocalAs = 'NULL';
|
||||
$deviceObj->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Do bgpLocalAs Update
|
||||
// Get the most common localAS (in theory there *should* be only one, but not always
|
||||
$localAsns = array_count_values(array_column($protocolsData, 'local_as'));
|
||||
arsort($localAsns);
|
||||
$bgpLocalAs = array_keys($localAsns)[0];
|
||||
|
||||
$deviceObj->bgpLocalAs = $bgpLocalAs;
|
||||
$deviceObj->save();
|
||||
|
||||
// Going through all BGP Peers
|
||||
$bgpPeerIds = [];
|
||||
|
||||
foreach ($protocolsData as $protocol) {
|
||||
$bgpPeer = BgpPeer::firstOrNew([
|
||||
'device_id' => $device['device_id'],
|
||||
'bgpPeerRemoteAs' => $protocol['neighbor_as'],
|
||||
'bgpLocalAddr' => $protocol['source_address'] ?: '0.0.0.0',
|
||||
'bgpPeerRemoteAddr' => $protocol['neighbor_address'],
|
||||
]);
|
||||
|
||||
$bgpPeer->device_id = $device['device_id'];
|
||||
$bgpPeer->astext = get_astext($protocol['neighbor_as']);
|
||||
$bgpPeer->bgpPeerIdentifier = $protocol['neighbor_id'] ?: '0.0.0.0';
|
||||
$bgpPeer->bgpPeerRemoteAs = $protocol['neighbor_as'];
|
||||
$bgpPeer->bgpPeerState = strtolower($protocol['bgp_state']);
|
||||
$bgpPeer->bgpPeerAdminStatus = str_replace('up', 'start', strtolower($protocol['protocol_state']));
|
||||
|
||||
if (isset($protocolData['last_error'])) {
|
||||
// Find the subcode if its there and set it
|
||||
foreach (trans('bgp.error_subcodes') as $mainCode => $subCodes) {
|
||||
foreach ($subCodes as $subCode => $message) {
|
||||
if ($message == $protocolData['last_error']) {
|
||||
$bgpPeer->bgpPeerLastErrorCode = $mainCode;
|
||||
$bgpPeer->bgpPeerLastErrorSubCode = $subCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bgpPeer->bgpPeerLastErrorText = $protocol['neighbor_id'] ?: '0.0.0.0';
|
||||
}
|
||||
|
||||
$bgpPeer->bgpLocalAddr = $protocol['source_address'] ?: '0.0.0.0';
|
||||
$bgpPeer->bgpPeerRemoteAddr = $protocol['neighbor_address'];
|
||||
$bgpPeer->bgpPeerDescr = $protocol['description'] ?: $protocol['name'];
|
||||
$bgpPeer->bgpPeerInUpdates = intval($protocol['route_change_stats']['import_updates']['accepted']);
|
||||
$bgpPeer->bgpPeerOutUpdates = intval($protocol['route_change_stats']['export_updates']['accepted']);
|
||||
$bgpPeer->bgpPeerInTotalMessages = intval($protocol['route_change_stats']['import_updates']['received']);
|
||||
$bgpPeer->bgpPeerOutTotalMessages = intval($protocol['route_change_stats']['export_updates']['received']);
|
||||
|
||||
$bgpPeer->bgpPeerFsmEstablishedTime = Carbon::parse($protocol['since'])->diffInSeconds(Carbon::now());
|
||||
$bgpPeer->bgpPeerInUpdateElapsedTime = Carbon::parse($protocol['since'])->diffInSeconds(Carbon::now());
|
||||
$bgpPeer->save();
|
||||
|
||||
echo PHP_EOL . $name . ': Processed peer AS' . $bgpPeer->bgpPeerRemoteAs . ' (' . $bgpPeer->astext . ')';
|
||||
|
||||
$bgpPeerIds[] = $bgpPeer->bgpPeer_id;
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
|
||||
// Clean up any bgpPeers that arent on the list for this device
|
||||
BgpPeer::where('device_id', $device['device_id'])->whereNotIn('bgpPeer_id', $bgpPeerIds)->delete();
|
Reference in New Issue
Block a user