Merge branch 'marcquark-frr_remove_ruby'

This commit is contained in:
Ad Schellevis
2020-12-22 11:28:00 +01:00
12 changed files with 1232 additions and 1326 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
PLUGIN_NAME= frr
PLUGIN_VERSION= 1.20
PLUGIN_VERSION= 1.21
PLUGIN_COMMENT= The FRRouting Protocol Suite
PLUGIN_DEPENDS= frr7 ruby
PLUGIN_DEPENDS= frr7
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
.include "../../Mk/plugins.mk"
+21
View File
@@ -11,6 +11,27 @@ switching and routing, Internet access routers, and Internet peering.
Plugin Changelog
================
1.21
* Deprecate Ruby parser for FRR diagnostic output, remove Ruby dependency
* Use FRR's json output where available
* Introduce Python parser for FRR output where json is not (yet) available
* Streamline diagnostic action names
* Streamline diagnostic API endpoint names
* Add diagnostic action and API endpoint for BGP neighbors
* Webif Diagnostics/General: Fix routing tables
* Webif Diagnostics/General: Align routing table style with other pages
* Webif Diagnostics/OSPF: Correctly display areas in "Overview" tab
* Webif Diagnostics/OSPF: Fix area rendering to "NaN" in "Route Table" tab
* Webif Diagnostics/OSPF: Use only one table in "Route Table" tab
* Webif Diagnostics/OSPF: Add mouseover hints to Type column in "Route Table" tab
* Webif Diagnostics/OSPF: Slight presentation adjustments in "Neighbor" tab
* Webif Diagnostics/OSPFv3: Fix age and sequence number rendering to "NaN" in "Database" tab
* Webif Diagnostics/BGP: Rename page from "BGPv4" to just "BGP"
* Webif Diagnostics/BGP: Replace "Overview" tab with distinct "IPv4 Routing Table" and "IPv6 Routing table" tabs
* Webif Diagnostics/BGP: Align routing table style with other pages
* Webif Diagnostics/BGP: Add Neighbors tab
1.20
* Allow to adjust reference cost for OSPF calculation
@@ -41,96 +41,111 @@ use OPNsense\Core\Config;
*/
class DiagnosticsController extends ApiControllerBase
{
/**
* show ip bgp
* @return array
*/
public function showipbgpAction()
private function getInformation(string $daemon, string $name, string $format): array
{
$backend = new Backend();
$response = json_decode(trim($backend->configdRun("quagga diag-bgp2")));
return array("response" => $response);
$response = $backend->configdRun("quagga diagnostics ".$daemon."_".$name.($format === "json" ? "_json" : ""));
return array("response" => ($format === "json" ? json_decode($response) : $response));
}
/**
* show ip bgp summary
* @return array
*/
public function showipbgpsummaryAction()
public function generalrunningconfigAction(): array
{
$backend = new Backend();
$response = $backend->configdRun("quagga diag-bgp summary");
return array("response" => $response);
return $this->getInformation("general", "running-config", "plain");
}
public function showrunningconfigAction()
public function generalrouteAction($format = "json"): array
{
$backend = new Backend();
$response = $backend->configdRun("quagga general-runningconfig");
return array("response" => $response);
$routes4 = $this->getInformation("general", "route4", $format)['response'];
$routes6 = $this->getInformation("general", "route6", $format)['response'];
if ($format === "json") {
return array("response" => array("ipv4" => $routes4, "ipv6" => $routes6));
} else {
return array("response" => $routes4.$routes6);
}
}
private function get_ospf_information($name)
public function generalroute4Action($format = "json"): array
{
$backend = new Backend();
return array("response" => json_decode(trim($backend->configdRun("quagga ospf-$name"))));
return $this->getInformation("general", "route4", $format);
}
private function get_ospf3_information($name)
public function generalroute6Action($format = "json"): array
{
$backend = new Backend();
return array("response" => json_decode(trim($backend->configdRun("quagga ospfv3-$name"))));
return $this->getInformation("general", "route6", $format);
}
// OSPFv2
public function ospfoverviewAction()
public function bgprouteAction($format = "json"): array
{
return $this->get_ospf_information('overview');
return $this->getInformation("bgp", "route", $format);
}
public function ospfneighborAction()
public function bgproute4Action($format = "json"): array
{
return $this->get_ospf_information('neighbor');
return $this->getInformation("bgp", "route4", $format);
}
public function ospfrouteAction()
public function bgproute6Action($format = "json"): array
{
return $this->get_ospf_information('route');
return $this->getInformation("bgp", "route6", $format);
}
public function ospfdatabaseAction()
public function bgpsummaryAction($format = "json"): array
{
return $this->get_ospf_information('database');
return $this->getInformation("bgp", "summary", $format);
}
public function ospfinterfaceAction()
public function bgpneighborsAction($format = "json"): array
{
return $this->get_ospf_information('interface');
return $this->getInformation("bgp", "neighbors", $format);
}
// OSPFv3
public function ospfv3overviewAction()
public function ospfoverviewAction($format = "json"): array
{
return $this->get_ospf3_information('overview');
return $this->getInformation("ospf", "overview", $format);
}
public function ospfv3neighborAction()
public function ospfneighborAction($format = "json"): array
{
return $this->get_ospf3_information('neighbor');
return $this->getInformation("ospf", "neighbor", $format);
}
public function ospfv3routeAction()
public function ospfrouteAction($format = "json"): array
{
return $this->get_ospf3_information('route');
return $this->getInformation("ospf", "route", $format);
}
public function ospfv3databaseAction()
public function ospfdatabaseAction($format = "json"): array
{
return $this->get_ospf3_information('database');
return $this->getInformation("ospf", "database", $format);
}
public function ospfv3interfaceAction()
public function ospfinterfaceAction($format = "json"): array
{
return $this->get_ospf3_information('interface');
return $this->getInformation("ospf", "interface", $format);
}
// General
private function get_general_information($name)
public function ospfv3overviewAction($format = "json"): array
{
$backend = new Backend();
return array("response" => json_decode(trim($backend->configdRun("quagga general-$name")), true));
return $this->getInformation("ospfv3", "overview", $format);
}
public function generalroutesAction()
public function ospfv3neighborAction($format = "json"): array
{
return $this->get_general_information('routes');
return $this->getInformation("ospfv3", "neighbor", $format);
}
public function generalroutes6Action()
public function ospfv3routeAction($format = "json"): array
{
return $this->get_general_information('routes6');
return $this->getInformation("ospfv3", "route", $format);
}
public function ospfv3databaseAction($format = "json"): array
{
return $this->getInformation("ospfv3", "database", $format);
}
public function ospfv3interfaceAction($format = "json"): array
{
return $this->getInformation("ospfv3", "interface", $format);
}
}
@@ -5,12 +5,12 @@
<OSPF VisibleName="OSPF" cssClass="fa fa-map fa-fw" url="/ui/quagga/ospf/index" order="20" />
<OSPFv3 VisibleName="OSPFv3" cssClass="fa fa-map fa-fw" url="/ui/quagga/ospf6/index" order="25" />
<!--<ISIS VisibleName="IS-IS" cssClass="fa fa-bolt fa-fw" url="/ui/quagga/isis/index" order="30" />-->
<BGP VisibleName="BGPv4" cssClass="fa fa-globe fa-fw" url="/ui/quagga/bgp/index" order="40" />
<BGP VisibleName="BGP" cssClass="fa fa-globe fa-fw" url="/ui/quagga/bgp/index" order="40" />
<Diagnostics VisibleName="Diagnostics" cssClass="fa fa-medkit fa-fw" order="50">
<General VisibleName="General" url="/ui/quagga/diagnostics/general" order="1" />
<OSPF VisibleName="OSPF" url="/ui/quagga/diagnostics/ospf" order="20" />
<OSPFv3 VisibleName="OSPFv3" url="/ui/quagga/diagnostics/ospfv3" order="25" />
<BGP VisibleName="BGPv4" url="/ui/quagga/diagnostics/bgp" order="40" />
<BGP VisibleName="BGP" url="/ui/quagga/diagnostics/bgp" order="40" />
<LOG VisibleName="Log" url="/ui/diagnostics/log/routing/frr" order="100" />
</Diagnostics>
</Routing>
@@ -32,82 +32,114 @@ POSSIBILITY OF SUCH DAMAGE.
{{ partial("layout_partials/base_form",['fields':diagnosticsForm,'id':'frm_diagnostics_settings'])}}
#}
<script type="text/x-template" id="overviewtpl">
<table>
<tr>
<td>{{ lang._('Table Version') }}</td>
<td><%= bgp_overview['table_version'] %></td>
</tr>
<tr>
<td>{{ lang._('Local Router ID') }}</td>
<td><%= bgp_overview['local_router_id'] %></td>
</tr>
</table>
<table>
<script type="text/x-template" id="routestpl">
<h2>{{ lang._('Table Version') }}: <%= tableVersion %></h2>
<table class="table table-striped">
<thead>
<tr>
<th>{{ lang._('Status') }}</th>
<th>{{ lang._('Network') }}</th>
<th>{{ lang._('Next Hop') }}</th>
<th>{{ lang._('Metric') }}</th>
<th>{{ lang._('LocPrf') }}</th>
<th>{{ lang._('Weight') }}</th>
<th>{{ lang._('Path') }}</th>
<th data-column-id="valid" data-type="boolean" data-formatter="boolean" data-width="5%">{{ lang._('Valid') }}</th>
<th data-column-id="best" data-type="boolean" data-formatter="boolean" data-width="5%">{{ lang._('Best') }}</th>
<th data-column-id="internal" data-type="boolean" data-formatter="boolean" data-width="6%">{{ lang._('Internal') }}</th>
<th data-column-id="network" data-type="string" data-width="21%">{{ lang._('Network') }}</th>
<th data-column-id="nexthop" data-type="string" data-width="21%">{{ lang._('Next Hop') }}</th>
<th data-column-id="metric" data-type="numeric" data-width="5%">{{ lang._('Metric') }}</th>
<th data-column-id="locprf" data-type="numeric" data-width="5%">{{ lang._('LocPrf') }}</th>
<th data-column-id="weight" data-type="numeric" data-width="6%">{{ lang._('Weight') }}</th>
<th data-column-id="path" data-type="string" data-width="16%">{{ lang._('Path') }}</th>
<th data-column-id="origin" data-type="string" data-formatter="origin" data-width="10%">{{ lang._('Origin') }}</th>
</tr>
</thead>
<tbody>
<% _.each(bgp_overview['output'], function (row) { %>
<tr>
<td>
<% _.each(row['status'], function(element) { %>
<abbr title="<%= translate(element['dn']) %>"><%= element['abb'] %></abbr>
<% }) %>
</td>
<td><%= row['Network'] %></td>
<td><%= row['Next Hop'] %></td>
<td><%= row['Metric'] %></td>
<td><%= row['LocPrf'] %></td>
<td><%= row['Weight'] %></td>
<td>
<% _.each(row['Path'], function(element) { %>
<abbr title="<%= translate(element['dn']) %>"><%= element['abb'] %></abbr>
<% }) %>
</td>
</tr>
<% _.forEach(routes, function(route_array, network) { %>
<% _.forEach(route_array, function(route) { %>
<% _.forEach(route['nexthops'], function(nexthop) { %>
<tr>
<td><%= (typeof(route['valid']) != "undefined" && route['valid']) %></td>
<td><%= (typeof(route['bestpath']) != "undefined" && route['bestpath']) %></td>
<td><%= (typeof(route['pathFrom']) != "undefined" && route['pathFrom'] == 'internal') %></td>
<td><%= network %></td>
<td><%= nexthop['ip'] %></td>
<td><%= route['metric'] %></td>
<td><%= route['locPrf'] %></td>
<td><%= route['weight'] %></td>
<td><%= (route['path'] == "" ? "{{ lang._('Internal') }}" : route['path']) %></td>
<td><%= route['origin'] %></td>
</tr>
<% }); %>
<% }); %>
<% }); %>
</tbody>
</table>
</script>
<script src="/ui/js/quagga/lodash.js"></script>
<script>
function translate(x) {
return x;
<script type="text/javascript" src="/ui/js/quagga/lodash.js"></script>
<script type="text/javascript">
let dataconverters = {
boolean: {
from: function (value) { return (value == 'true') || (value == true); },
to: function (value) { return value; }
}
}
let dataformatters = {
boolean: function (column, row) {
if (row[column.id]) {
return "<span class=\"fa fa-check\" data-value=\"1\" data-row-id=\"" + row.uuid + "\"></span>";
} else {
return "<span class=\"fa fa-times\" data-value=\"0\" data-row-id=\"" + row.uuid + "\"></span>";
}
},
origin: function(column, row) {
return (row[column.id] === 'incomplete' ? '<abbr title="{{ lang._('Incomplete') }}">&quest;</abbr>' : row[column.id]);
}
}
$(document).ready(function() {
updateServiceControlUI('quagga');
ajaxCall(url="/api/quagga/diagnostics/showipbgp", sendData={}, callback=function(data,status) {
content = _.template($('#overviewtpl').html())(data['response'])
$('#overview').html(content)
ajaxCall(url="/api/quagga/diagnostics/bgproute4", sendData={}, callback=function(data, status) {
content = _.template($('#routestpl').html())(data['response']);
$('#routing').html(content);
$('#routing table').bootgrid({
converters: dataconverters,
formatters: dataformatters
});
});
ajaxCall(url="/api/quagga/diagnostics/showipbgpsummary", sendData={}, callback=function(data,status) {
$("#summarycontent").text(data['response']);
ajaxCall(url="/api/quagga/diagnostics/bgproute6", sendData={}, callback=function(data, status) {
content = _.template($('#routestpl').html())(data['response']);
$('#routing6').html(content);
$('#routing6 table').bootgrid({
converters: dataconverters,
formatters: dataformatters
});
});
ajaxCall(url="/api/quagga/diagnostics/bgpneighbors/plain", sendData={}, callback=function(data, status) {
$('#neighborscontent').text(data['response']);
});
ajaxCall(url="/api/quagga/diagnostics/bgpsummary/plain", sendData={}, callback=function(data, status) {
$("#summarycontent").text(data['response']);
});
});
</script>
<!-- Navigation bar -->
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#overview">{{ lang._('Overview') }}</a></li>
<li><a data-toggle="tab" href="#summary">{{ lang._('Summary') }}</a></li>
<li class="active"><a data-toggle="tab" href="#routing">IPv4 {{ lang._('Routing Table') }}</a></li>
<li><a data-toggle="tab" href="#routing6">IPv6 {{ lang._('Routing Table') }}</a></li>
<li><a data-toggle="tab" href="#neighbors">{{ lang._('Neighbors') }}</a></li>
<li><a data-toggle="tab" href="#summary">{{ lang._('Summary') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="overview" class="tab-pane fade in active">
{{ lang._('loading...') }}
</div>
<div id="summary" class="tab-pane fade in">
<pre id="summarycontent"></pre>
</div>
<div id="routing" class="tab-pane fade in active"></div>
<div id="routing6" class="tab-pane fade in"></div>
<div id="neighbors" class="tab-pane fade in">
<pre id="neighborscontent"></pre>
</div>
<div id="summary" class="tab-pane fade in">
<pre id="summarycontent"></pre>
</div>
</div>
@@ -28,106 +28,115 @@ POSSIBILITY OF SUCH DAMAGE.
#}
<script src="/ui/js/quagga/lodash.js"></script>
<script type="text/x-template" id="routestpl">
<table>
<thead>
<tr>
<th data-column-id="code" data-type="raw">{{ lang._('Code') }}</th>
<th data-column-id="network" data-type="string">{{ lang._('Network') }}</th>
<th data-column-id="ad" data-type="numeric">{{ lang._('Administrative Distance') }}</th>
<th data-column-id="metric" data-type="numeric">{{ lang._('Metric') }}</th>
<th data-column-id="interface" data-type="string">{{ lang._('Interface') }}</th>
<th data-column-id="via" data-type="string">{{ lang._('Via') }}</th>
<th data-column-id="time" data-type="string">{{ lang._('Time') }}</th>
</tr>
</thead>
<tbody>
<% _.each(general_routes, function(entry) { %>
<table class="table table-striped">
<thead>
<tr>
<td>
<% _.each(entry['code'], function(code) { %>
<abbr title="<%= translate(code['long']) %>"><%= (code['short']) %></abbr>
<% }); %>
</td>
<td><%= entry['network'] %></td>
<td><%= entry['ad'] %></td>
<td><%= entry['metric'] %></td>
<td><%= entry['interface'] %></td>
<td><%= entry['via'] %></td>
<td><%= entry['time'] %></td>
<th data-column-id="code" data-type="string" data-formatter="code">{{ lang._('Code') }}</th>
<th data-column-id="selected" data-type="boolean" data-visible="false">{{ lang._('Selected') }}</th>
<th data-column-id="installed" data-type="boolean" data-visible="false">{{ lang._('Installed') }}</th>
<th data-column-id="network" data-type="string">{{ lang._('Network') }}</th>
<th data-column-id="ad" data-type="numeric">{{ lang._('Administrative Distance') }}</th>
<th data-column-id="metric" data-type="numeric">{{ lang._('Metric') }}</th>
<th data-column-id="interface" data-type="string">{{ lang._('Interface') }}</th>
<th data-column-id="via" data-type="string">{{ lang._('Via') }}</th>
<th data-column-id="time" data-type="string">{{ lang._('Time') }}</th>
</tr>
<% }); %>
</tbody>
</table>
</thead>
<tbody>
<% _.forEach(routes, function(route_array, network) { %>
<% _.forEach(route_array, function(route) { %>
<% _.forEach(route['nexthops'], function(nexthop) { %>
<tr>
<td><%= route['protocol'] %></td>
<td><%= (typeof(route['selected']) != "undefined" && route['selected']) %></td>
<td><%= (typeof(route['installed']) != "undefined" && route['installed']) %></td>
<td><%= network %></td>
<td><%= route['distance'] %></td>
<td><%= route['metric'] %></td>
<td><%= nexthop['interfaceName'] %></td>
<td><%= (typeof(nexthop['ip']) != "undefined" ? nexthop['ip'] : '{{ lang._('Directly Attached') }}') %></td>
<td><%= route['uptime'] %></td>
</tr>
<% }); %>
<% }); %>
<% }); %>
</tbody>
</table>
</script>
<script>
function translate(content) {
tr = {};
tr['kernel route'] = '{{ lang._('Kernel Route') }}';
tr['FIB route'] = '{{ lang._('FIB Route') }}';
tr['connected'] = '{{ lang._('Connected') }}';
tr['selected route'] = '{{ lang._('Selected Route') }}';
tr['OSPF'] = '{{ lang._('OSPF') }}';
tr['RIP'] = '{{ lang._('RIP') }}';
tr['BGP'] = '{{ lang._('BGP') }}';
if (_.has(tr,content))
{
return tr[content];
}
else
{
return content;
<script type="text/javascript">
function translateProtocol(data) {
let tr = [];
// routing table tab
tr['kernel'] = {short: 'K', long: '{{ lang._('Kernel') }}'};
tr['connected'] = {short: 'C', long: '{{ lang._('Connected') }}'};
tr['bgp'] = {short: 'B', long: '{{ lang._('BGP') }}'};
tr['ospf'] = {short: 'O', long: '{{ lang._('OSPF') }}'};
tr['ospf6'] = {short: 'O', long: '{{ lang._('OSPFv3') }}'};
return _.has(tr,data) ? tr[data] : data;
}
let dataconverters = {
boolean: {
from: function (value) { return (value == 'true') || (value == true); },
to: function (value) { return value; }
}
}
dataconverters = {
boolean: {
from: function (value) { return (value == 'true') || (value == true); },
to: function (value) { return checkmark(value) }
},
raw: {
from: function (value) {
console.log(value)
return value
},
to: function (value) {
console.log(value);
return value
}
}
}
let dataformatters = {
code: function(column, row) {
let result = row.code;
let protocol = translateProtocol(row.code);
if(typeof(protocol) != "string") result = '<abbr title="' + protocol['long'] + '">' + protocol['short'] + '</abbr> ';
if(row.selected) result += '<abbr title="{{ lang._('Selected') }}">&gt;</abbr> ';
if(row.installed) result += '<abbr title="{{ lang._('FIB') }}">&ast;</abbr>';
return result;
}
};
$(document).ready(function() {
updateServiceControlUI('quagga');
ajaxCall(url="/api/quagga/diagnostics/generalroutes", sendData={}, callback=function(data,status) {
content = _.template($('#routestpl').html())(data['response'])
$('#routing').html(content)
//$('#routing table').bootgrid({converters: dataconverters})
});
ajaxCall(url="/api/quagga/diagnostics/generalroutes6", sendData={}, callback=function(data,status) {
content = _.template($('#routestpl').html())({general_routes: data['response']['general_routes6']})
$('#routing6').html(content)
//$('#routing6 table').bootgrid({converters: dataconverters})
});
ajaxCall(url="/api/quagga/diagnostics/showrunningconfig", sendData={}, callback=function(data,status) {
$("#runningconfig").text(data['response']);
ajaxCall(url="/api/quagga/diagnostics/generalroute", sendData={}, callback=function(data, status) {
let content = _.template($('#routestpl').html())({
routes: data['response']['ipv4']
});
$('#routing').html(content);
$('#routing table').bootgrid({
converters: dataconverters,
formatters: dataformatters
});
content = _.template($('#routestpl').html())({
routes: data['response']['ipv6']
});
$('#routing6').html(content);
$('#routing6 table').bootgrid({
converters: dataconverters,
formatters: dataformatters
});
});
ajaxCall(url="/api/quagga/diagnostics/generalrunningconfig", sendData={}, callback=function(data, status) {
$("#runningconfig").text(data['response']);
});
});
</script>
<!-- Navigation bar -->
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#routing">{{ lang._('IPv4 Routes') }}</a></li>
<li><a data-toggle="tab" href="#routing6">{{ lang._('IPv6 Routes') }}</a></li>
<li><a data-toggle="tab" href="#showrun">{{ lang._('Running Configuration') }}</a></li>
<li class="active"><a data-toggle="tab" href="#routing">{{ lang._('IPv4 Routes') }}</a></li>
<li><a data-toggle="tab" href="#routing6">{{ lang._('IPv6 Routes') }}</a></li>
<li><a data-toggle="tab" href="#showrun">{{ lang._('Running Configuration') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="routing" class="tab-pane fade in active"></div>
<div id="routing6" class="tab-pane fade in"></div>
<div id="showrun" class="tab-pane fade in">
<pre id="runningconfig"></pre>
</div>
<div id="routing" class="tab-pane fade in active"></div>
<div id="routing6" class="tab-pane fade in"></div>
<div id="showrun" class="tab-pane fade in">
<pre id="runningconfig"></pre>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -130,7 +130,7 @@ POSSIBILITY OF SUCH DAMAGE.
<th data-column-id="lsid" data-type="string">{{ lang._('LS ID') }}</th>
<th data-column-id="advrouter" data-type="string">{{ lang._('Advertising Router') }}</th>
<th data-column-id="age" data-type="numeric">{{ lang._('Age') }}</th>
<th data-column-id="seqnr" data-type="numeric">{{ lang._('Sequence Number') }}</th>
<th data-column-id="seqnr" data-type="string">{{ lang._('Sequence Number') }}</th>
<th data-column-id="payload" data-type="string">{{ lang._('Payload') }}</th>
</tr>
</thead>
@@ -163,7 +163,7 @@ POSSIBILITY OF SUCH DAMAGE.
<th data-column-id="lsid" data-type="string">{{ lang._('LS ID') }}</th>
<th data-column-id="advrouter" data-type="string">{{ lang._('Advertising Router') }}</th>
<th data-column-id="age" data-type="numeric">{{ lang._('Age') }}</th>
<th data-column-id="seqnr" data-type="numeric">{{ lang._('Sequence Number') }}</th>
<th data-column-id="seqnr" data-type="string">{{ lang._('Sequence Number') }}</th>
<th data-column-id="payload" data-type="string">{{ lang._('Payload') }}</th>
</tr>
</thead>
@@ -192,7 +192,7 @@ POSSIBILITY OF SUCH DAMAGE.
<th data-column-id="lsid" data-type="string">{{ lang._('LS ID') }}</th>
<th data-column-id="advrouter" data-type="string">{{ lang._('Advertising Router') }}</th>
<th data-column-id="age" data-type="numeric">{{ lang._('Age') }}</th>
<th data-column-id="seqnr" data-type="numeric">{{ lang._('Sequence Number') }}</th>
<th data-column-id="seqnr" data-type="string">{{ lang._('Sequence Number') }}</th>
<th data-column-id="payload" data-type="string">{{ lang._('Payload') }}</th>
</tr>
</thead>
@@ -0,0 +1,445 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2020 Marc Leuser
Copyright (c) 2020 Ad Schellevis <ad@opnsense.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import argparse
import ujson
import re
from typing import List, Dict
from lib import VtySH
class Re:
""" Custom regex helper class (source https://stackoverflow.com/a/4980181)
Use to conveniently build switch-case like constructs using if/elif
"""
def __init__(self):
self.last_match = None
def match(self, pattern, text):
self.last_match = re.match(pattern, text)
return self.last_match
def search(self, pattern, text):
self.last_match = re.search(pattern, text)
return self.last_match
class FRRTableReader:
def __init__(self, titles: List[str] = None):
self.titles = titles if titles is not None else list()
self.columns = list()
def read_header(self, line: str, start_without_title: bool = False, start_without_title_name: str = 'status'):
# we're going to create a list of columns. each entry contains title, start index and end index for easy parsing
self.columns = []
# the first column's title may sometimes be empty in FRR's output. we'll have to give it a name though
if start_without_title:
self.columns.append({
'title': start_without_title_name,
'start_index': 0,
'end_index': None
})
# fill the list with subsequent columns
for title in self.titles:
try:
# find the start index of the current title in the header
start_index = line.index(title)
except ValueError:
# just skip the column if it can't be found
continue
# last column's end index is this column's start index
if self.columns:
self.columns[-1]['end_index'] = start_index
# add the current column to the list
self.columns.append({
'title': title,
'start_index': start_index,
'end_index': None
})
def read_line(self, line: str) -> Dict[str, str]:
result = {}
# sanity check: the line has to be long enough to contain all columns,
# so its length must be greater than the last column's start index
if len(line) > self.columns[-1]['start_index']:
for column in self.columns:
# use the column's name as dict key and just extract the data from start to end index
result[column['title'].strip()] = line[column['start_index']:column['end_index']].strip()
return result
class DaemonError(Exception):
pass
class Daemon:
def __init__(self, vtysh: VtySH):
self.vtysh = vtysh
self.myre = Re()
def _show(self, suffix: str):
# execute the command and filter out empty lines so subsequent iterations don't have to deal with them
return list(filter(None, self.vtysh.execute(command='show ' + suffix, translate=bytes.decode).split('\n')))
class OSPF(Daemon):
def _show(self, suffix: str):
return super()._show('ip ospf ' + suffix)
def database(self):
db = {}
# table reader for Route Link States
rltr = FRRTableReader(titles=['Link ID', 'ADV Router', 'Age', 'Seq#', 'CkSum', 'Link count'])
# table reader for Net Link States
nltr = FRRTableReader(titles=['Link ID', 'ADV Router', 'Age', 'Seq#', 'CkSum'])
# table reader for Summary Link States
sltr = FRRTableReader(titles=['Link ID', 'ADV Router', 'Age', 'Seq#', 'CkSum', 'Route\n'])
# table reader for AS External Link States (columns are identical to Summary Link States)
eltr = sltr
# get the FRR output
lines = self._show('database')
# placeholder for the active table reader for the coming line
tr = None
# whether or not the header for the current section has already been parsed
header_parsed = False
# the current router
router = None
# the current area
area = None
# the current mode
mode = None
for line in lines:
if line.startswith(' '):
# this is a heading
heading = line.strip()
header_parsed = False
# this is going to be dirty
if self.myre.search(r'OSPF Router with ID \(([\.\d]+)\)', heading):
router = self.myre.last_match.group(1)
if router not in db:
db[router] = {}
mode = 'router'
elif self.myre.search(r'Router Link States \(Area ([\.\d]+)\)', heading):
mode = 'router_link_state_area'
area = self.myre.last_match.group(1)
if mode not in db[router]:
db[router][mode] = {}
if area not in db[router][mode]:
db[router][mode][area] = []
tr = rltr
elif self.myre.search(r'Net Link States \(Area ([\.\d]+)\)', heading):
mode = 'net_link_state_area'
area = self.myre.last_match.group(1)
if mode not in db[router]:
db[router][mode] = {}
if area not in db[router][mode]:
db[router][mode][area] = []
tr = nltr
elif self.myre.search(r'Summary Link States \(Area ([\.\d]+)\)', heading):
mode = 'summary_link_state_area'
area = self.myre.last_match.group(1)
if mode not in db[router]:
db[router][mode] = {}
if area not in db[router][mode]:
db[router][mode][area] = []
tr = sltr
elif heading == 'AS External Link States':
mode = 'external_states'
if mode not in db[router]:
db[router][mode] = []
tr = eltr
else:
raise DaemonError('failed to parse heading: ' + heading)
# told you.
else:
if not header_parsed:
if mode in ['summary_link_state_area', 'external_states']:
# workaround because "Route" matches on "Router" and breaks the offset parsing logic
# stay consistent with the previous script, add a trailing newline
line += '\n'
tr.read_header(line)
header_parsed = True
else:
if mode == 'router':
raise DaemonError(
'attempting to parse a table row but the mode is \'router\'. '
'please debug the FRR output:\n\n\n' + line
)
elif mode == 'external_states':
db[router][mode].append(tr.read_line(line))
else:
db[router][mode][area].append(tr.read_line(line))
return db
class OSPFv3(Daemon):
def _show(self, suffix: str):
return super()._show('ipv6 ospf6 ' + suffix)
def database(self):
database = {}
lines = map(str.strip, self._show('database'))
# table reader
tr = FRRTableReader(titles=["Type", "LSId", "AdvRouter", " Age", " SeqNum", " Payload"])
header_parsed = False
interface = None
area = None
mode = None
for line in lines:
if self.myre.search(r'Area Scoped Link State Database \(Area (.*)\)', line):
header_parsed = False
mode = 'scoped_link_db'
area = self.myre.last_match.group(1)
if mode not in database:
database[mode] = {}
if area not in database[mode]:
database[mode][area] = []
elif self.myre.search(r'I\/F Scoped Link State Database \(I\/F (\S+) in Area (.*)\)', line):
header_parsed = False
mode = 'if_scoped_link_state'
interface = self.myre.last_match.group(1)
area = self.myre.last_match.group(2)
if mode not in database:
database[mode] = {}
if interface not in database[mode]:
database[mode][interface] = {}
if area not in database[mode][interface]:
database[mode][interface][area] = []
elif line == 'AS Scoped Link State Database':
header_parsed = False
mode = 'as_scoped'
if mode not in database:
database[mode] = []
else:
if not header_parsed:
tr.read_header(line)
header_parsed = True
else:
if mode == 'scoped_link_db':
database[mode][area].append(tr.read_line(line))
elif mode == 'if_scoped_link_state':
database[mode][interface][area].append(tr.read_line(line))
elif mode == 'as_scoped':
database[mode].append(tr.read_line(line))
else:
raise DaemonError('invalid mode, failed to parse line: ' + line)
return database
def route(self):
route = []
lines = map(str.strip, self._show('route'))
for line in lines:
columns = re.split(r'\s+', line)
route.append({
'f1': columns[0],
'f2': columns[1],
'network': columns[2],
'gateway': columns[3],
'interface': columns[4],
'time': columns[5],
})
return route
def interface(self):
interface = {}
lines = map(str.strip, self._show('interface'))
current_if = None
for line in lines:
if self.myre.search(r'(\S+) is (down|up), type ([A-Z]+)', line):
current_if = self.myre.last_match.group(1)
interface[current_if] = {
'up': True if self.myre.last_match.group(2) == 'up' else False,
'type': self.myre.last_match.group(3),
'enabled': True
}
elif self.myre.search(r'Interface ID: (\d+)', line):
interface[current_if]['id'] = self.myre.last_match.group(1)
elif self.myre.search(r'OSPF not enabled on this interface', line):
interface[current_if]['enabled'] = False
elif self.myre.search(r'Instance ID (\d+), Interface MTU (\d+) \(autodetect: (\d+)\)', line):
interface[current_if]['instance_id'] = int(self.myre.last_match.group(1))
interface[current_if]['interface_mtu'] = int(self.myre.last_match.group(2))
interface[current_if]['interface_mtu_autodetect'] = int(self.myre.last_match.group(3))
elif self.myre.search(r'(inet |inet6): (\S+)', line):
family = 'IPv6' if self.myre.last_match.group(1) == 'inet6' else 'IPv4'
if family not in interface[current_if]:
interface[current_if][family] = []
interface[current_if][family].append(self.myre.last_match.group(2))
elif self.myre.search(r'MTU mismatch detection: (en|dis)abled', line):
interface[current_if]['mtu_mismatch_detection'] = True if self.myre.last_match.group(
1) == 'en' else False
elif self.myre.search(r'DR: (\S+) BDR: (\S+)', line):
interface[current_if]['designated_router'] = self.myre.last_match.group(1)
interface[current_if]['backup_designated_router'] = self.myre.last_match.group(2)
elif self.myre.search(r'State (\S+), Transmit Delay (\d+) sec, Priority (\d+)', line):
interface[current_if]['state'] = self.myre.last_match.group(1)
interface[current_if]['transmit_delay'] = int(self.myre.last_match.group(2))
interface[current_if]['priority'] = int(self.myre.last_match.group(3))
elif self.myre.search(r'Number of I\/F scoped LSAs is (\d+)', line):
interface[current_if]['number_if_scoped_lsas'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'(\d+) Pending LSAs for (\S+) in Time ([\d:]+)(?: (.*))', line):
if 'pending_lsas' not in interface[current_if]:
interface[current_if]['pending_lsas'] = {}
interface[current_if]['pending_lsas'][self.myre.last_match.group(2)] = {
'time': self.myre.last_match.group(3),
'count': self.myre.last_match.group(1),
'flags': self.myre.last_match.group(4)
}
elif self.myre.search(r'Hello (\d+), Dead (\d+), Retransmit (\d+)', line):
interface[current_if]['timers'] = {
'hello': int(self.myre.last_match.group(1)),
'dead': int(self.myre.last_match.group(2)),
'retransmit': int(self.myre.last_match.group(3))
}
elif self.myre.search(r'Area ID (\S+), Cost (\d+)', line):
if 'area_cost' not in interface[current_if]:
interface[current_if]['area_cost'] = []
interface[current_if]['area_cost'].append({
'area': self.myre.last_match.group(1),
'cost': int(self.myre.last_match.group(2))
})
elif line in ['Internet Address:', 'Timer intervals configured:']:
# ignore these strings
pass
else:
raise DaemonError('failed to parse line: ' + line)
return interface
def neighbor(self):
neighbors = []
lines = self._show('neighbor')
tr = FRRTableReader(titles=['Neighbor ID', 'Pri', 'DeadTime', 'State/IfState', 'Duration I/F[State]'])
ll = lines.pop(0)
tr.read_header(ll)
for line in lines:
neighbor = tr.read_line(line)
neighbor['Pri'] = int(neighbor['Pri'])
neighbors.append(neighbor)
return neighbors
def overview(self):
overview = {'areas': {}}
lines = map(str.strip, self._show(''))
current_area = None
for line in lines:
if self.myre.search(r'OSPFv3 Routing Process \((\d+)\) with Router-ID ([\d\.]+)', line):
overview['router_id'] = self.myre.last_match.group(2)
overview['routing_process'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'Initial SPF scheduling delay (\d+) millisec\(s\)', line):
overview['initial_spf_scheduling_delay'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'(Min|Max)imum hold time between consecutive SPFs (\d+) milli?second\(s\)', line):
if 'hold_time' not in overview:
overview['hold_time'] = {}
overview['hold_time'][self.myre.last_match.group(1).lower()] = int(self.myre.last_match.group(2))
elif line == 'This router is an ASBR (injecting external routing information)':
overview['asbr'] = True
elif self.myre.search(r'SPF timer is (.*)', line):
overview['spf_timer'] = self.myre.last_match.group(1)
elif self.myre.search(r'Running (.*)', line):
overview['running_time'] = self.myre.last_match.group(1)
elif self.myre.search(r'Number of AS scoped LSAs is (\d+)', line):
overview['number_as_scoped'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'Hold time multiplier is currently (\d+)', line):
overview['current_hold_time_multipier'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'Number of areas in this router is (\d+)', line):
overview['number_of_areas'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'^Area ([\d\.]*)', line):
current_area = self.myre.last_match.group(1)
overview['areas'][current_area] = {}
elif self.myre.search(r'Interface attached to this area: (.*)', line):
overview['areas'][current_area]['interfaces'] = self.myre.last_match.group(1).split(' ')
elif self.myre.search(r'Number of Area scoped LSAs is (.*)', line):
overview['areas'][current_area]['number_lsas'] = int(self.myre.last_match.group(1))
elif self.myre.search(r'LSA minimum arrival (.*)', line) or \
self.myre.search(r'SPF algorithm last executed (.*)', line) or \
self.myre.search(r'Last SPF duration (.*)', line) or \
self.myre.search(r'SPF last executed (.*)', line) or \
self.myre.search(r'Number of Area scoped LSAs is (.*)', line):
# skip these lines
pass
else:
raise DaemonError('failed to parse line: ' + line)
return overview
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Python port of the OPNsense FRR output parser.')
parser.add_argument('-d', '--ospf-database', help='Prints the OSPF Database', action='store_true')
parser.add_argument('-D', '--ospfv3-database', help='Prints the OSPFv3 Database', action='store_true')
parser.add_argument('-t', '--ospfv3-route', help='Prints the OSPFv3 routing table', action='store_true')
parser.add_argument('-I', '--ospfv3-interface', help='Prints OSPFv3 interface information', action='store_true')
parser.add_argument('-N', '--ospfv3-neighbor', help='Prints OSPFv3 neighbor information', action='store_true')
parser.add_argument('-O', '--ospfv3-overview', help='Prints an OSPFv3 Summary', action='store_true')
args = parser.parse_args()
# initialize VtySH and parser objects
main_vtysh = VtySH()
ospf = OSPF(main_vtysh)
ospfv3 = OSPFv3(main_vtysh)
main_result = {}
if args.ospf_database:
main_result['ospf_database'] = ospf.database()
elif args.ospfv3_database:
main_result['ospfv3_database'] = ospfv3.database()
elif args.ospfv3_route:
main_result['ospfv3_route'] = ospfv3.route()
elif args.ospfv3_interface:
main_result['ospfv3_interface'] = ospfv3.interface()
elif args.ospfv3_neighbor:
main_result['ospfv3_neighbors'] = ospfv3.neighbor()
elif args.ospfv3_overview:
main_result['ospfv3_overview'] = ospfv3.overview()
print(ujson.dumps(main_result, escape_forward_slashes=False))
@@ -1,20 +0,0 @@
#!/bin/sh
case "$1" in
bgp)
vtysh -d bgpd -c "show ip bgp"
;;
summary)
vtysh -d bgpd -c "show bgp summary"
;;
neighbor)
vtysh -d bgpd -c "show ip bgp neighbors $2"
;;
neighbor-adv)
vtysh -d bgpd -c "show ip bgp neighbors $2 advertised-routes"
;;
*)
echo "Usage: $0 bgp|summary|neighbor <ip>|neighbor-adv <ip>"
exit 1
esac
exit 0
File diff suppressed because it is too large Load Diff
@@ -22,92 +22,260 @@ parameters:
type:script_output
message:request quagga
[diag-bgp]
command:/usr/local/opnsense/scripts/quagga/diag-bgp.sh
parameters:%s
type:script_output
message:bgp diagnostics
[diag-bgp2]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --bgp-overview
[diagnostics.general_running-config]
command:/usr/local/bin/vtysh -c "show running-config"
parameters:
type:script_output
message:bgp diagnostics
message:FRR diagnosticts "show running-config"
[ospf-database]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-database
[diagnostics.general_route4]
command:/usr/local/bin/vtysh -c "show ip route"
parameters:
type:script_output
message: Shows the OSPF database
message:FRR diagnosticts "show ip route"
[ospf-route]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-route
[diagnostics.general_route4_json]
command:/usr/local/bin/vtysh -c "show ip route json"
parameters:
type:script_output
message: print OSPF routing table
message:FRR diagnosticts "show ip route json"
[ospf-interface]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-interface
[diagnostics.general_route6]
command:/usr/local/bin/vtysh -c "show ipv6 route"
parameters:
type:script_output
message: print OSPF interface information
message:FRR diagnosticts "show ipv6 route"
[ospf-neighbor]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-neighbor
[diagnostics.general_route6_json]
command:/usr/local/bin/vtysh -c "show ipv6 route json"
parameters:
type:script_output
message: Print OSPF neighbors
message:FRR diagnosticts "show ipv6 route json"
[ospf-overview]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospf-overview
[diagnostics.bgp_route]
command:exit 1
parameters:
type:script_output
message: Print OSPF summary
message:FRR diagnostics "show bgp all" (not implemented)
[ospfv3-database]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-database
[diagnostics.bgp_route_json]
command:/usr/local/bin/vtysh -c "show bgp all json"
parameters:
type:script_output
message: Shows the OSPF database
message:FRR diagnostics "show bgp all json" (not implemented)
[ospfv3-route]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-route
[diagnostics.bgp_route4]
command:/usr/local/bin/vtysh -c "show bgp ipv4"
parameters:
type:script_output
message: print OSPF routing table
message:FRR diagnostics "show bgp ipv4"
[ospfv3-interface]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-interface
[diagnostics.bgp_route4_json]
command:/usr/local/bin/vtysh -c "show bgp ipv4 json"
parameters:
type:script_output
message: print OSPF interface information
message:FRR diagnostics "show bgp ipv4 json"
[ospfv3-neighbor]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-neighbor
[diagnostics.bgp_route6]
command:/usr/local/bin/vtysh -c "show bgp ipv6"
parameters:
type:script_output
message: Print OSPF neighbors
message:FRR diagnostics "show bgp ipv6"
[ospfv3-overview]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --ospfv3-overview
[diagnostics.bgp_route6_json]
command:/usr/local/bin/vtysh -c "show bgp ipv6 json"
parameters:
type:script_output
message: Print OSPF summary
message:FRR diagnostics "show bgp ipv6 json"
[general-routes]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --general-routes
[diagnostics.bgp_summary]
command:/usr/local/bin/vtysh -c "show bgp summary"
parameters:
type:script_output
message: Print IPv4 Routing Table
message:FRR diagnostics "show bgp summary"
[general-routes6]
command:/usr/local/opnsense/scripts/quagga/quagga.rb --general-routes6
[diagnostics.bgp_summary_json]
command:/usr/local/bin/vtysh -c "show bgp summary json"
parameters:
type:script_output
message: Print IPv6 Routing Table
message:FRR diagnostics "show bgp summary json"
[general-runningconfig]
command:/usr/local/bin/vtysh -c "show run"
[diagnostics.bgp_summary4]
command:/usr/local/bin/vtysh -c "show bgp ipv4 summary"
parameters:
type:script_output
message: Show running configuration
message:FRR diagnostics "show bgp ipv4 summary"
[diagnostics.bgp_summary4_json]
command:/usr/local/bin/vtysh -c "show bgp ipv4 summary json"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv4 summary json"
[diagnostics.bgp_summary6]
command:/usr/local/bin/vtysh -c "show bgp ipv6 summary"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv6 summary"
[diagnostics.bgp_summary6_json]
command:/usr/local/bin/vtysh -c "show bgp ipv6 summary json"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv6 summary json"
[diagnostics.bgp_neighbors]
command:/usr/local/bin/vtysh -c "show bgp neighbors"
parameters:
type:script_output
message:FRR diagnostics "show bgp neighbors"
[diagnostics.bgp_neighbors_json]
command:/usr/local/bin/vtysh -c "show bgp neighbors json"
parameters:
type:script_output
message:FRR diagnostics "show bgp neighbors json"
[diagnostics.bgp_neighbors4]
command:/usr/local/bin/vtysh -c "show bgp ipv4 neighbors"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv4 neighbors"
[diagnostics.bgp_neighbors4_json]
command:/usr/local/bin/vtysh -c "show bgp ipv4 neighbors json"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv4 neighbors json"
[diagnostics.bgp_neighbors6]
command:/usr/local/bin/vtysh -c "show bgp ipv6 neighbors"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv6 neighbors"
[diagnostics.bgp_neighbors6_json]
command:/usr/local/bin/vtysh -c "show bgp ipv6 neighbors json"
parameters:
type:script_output
message:FRR diagnostics "show bgp ipv6 neighbors json"
[diagnostics.ospf_overview]
command:/usr/local/bin/vtysh -c "show ip ospf"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf"
[diagnostics.ospf_overview_json]
command:/usr/local/bin/vtysh -c "show ip ospf json"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf json"
[diagnostics.ospf_neighbor]
command:/usr/local/bin/vtysh -c "show ip ospf neighbor"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf neighbor"
[diagnostics.ospf_neighbor_json]
command:/usr/local/bin/vtysh -c "show ip ospf neighbor json"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf neighbor json"
[diagnostics.ospf_route]
command:/usr/local/bin/vtysh -c "show ip ospf route"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf route"
[diagnostics.ospf_route_json]
command:/usr/local/bin/vtysh -c "show ip ospf route json"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf route json"
[diagnostics.ospf_database]
command:/usr/local/bin/vtysh -c "show ip ospf database"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf database"
[diagnostics.ospf_database_json]
command:/usr/local/opnsense/scripts/frr/legacy-diagnostics.py --ospf-database
parameters:
type:script_output
message:FRR diagnostics "show ip ospf database json"
[diagnostics.ospf_interface]
command:/usr/local/bin/vtysh -c "show ip ospf interface"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf interface"
[diagnostics.ospf_interface_json]
command:/usr/local/bin/vtysh -c "show ip ospf interface json"
parameters:
type:script_output
message:FRR diagnostics "show ip ospf interface json"
[diagnostics.ospfv3_overview]
command:/usr/local/bin/vtysh -c "show ipv6 ospf6"
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6"
[diagnostics.ospfv3_overview_json]
command:/usr/local/opnsense/scripts/frr/legacy-diagnostics.py --ospfv3-overview
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 json"
[diagnostics.ospfv3_neighbor]
command:/usr/local/bin/vtysh -c "show ipv6 ospf6 neighbor"
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 neighbor"
[diagnostics.ospfv3_neighbor_json]
command:/usr/local/opnsense/scripts/frr/legacy-diagnostics.py --ospfv3-neighbor
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 neighbor json"
[diagnostics.ospfv3_route]
command:/usr/local/bin/vtysh -c "show ipv6 ospf6 route"
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 route"
[diagnostics.ospfv3_route_json]
command:/usr/local/opnsense/scripts/frr/legacy-diagnostics.py --ospfv3-route
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 route json"
[diagnostics.ospfv3_database]
command:/usr/local/bin/vtysh -c "show ipv6 ospf6 database"
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 database"
[diagnostics.ospfv3_database_json]
command:/usr/local/opnsense/scripts/frr/legacy-diagnostics.py --ospfv3-database
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 database json"
[diagnostics.ospfv3_interface]
command:/usr/local/bin/vtysh -c "show ipv6 ospf6 interface"
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 interface"
[diagnostics.ospfv3_interface_json]
command:/usr/local/opnsense/scripts/frr/legacy-diagnostics.py --ospfv3-interface
parameters:
type:script_output
message:FRR diagnostics "show ipv6 ospf6 interface json"