mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
Merge branch 'marcquark-frr_remove_ruby'
This commit is contained in:
+2
-2
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+70
-55
@@ -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') }}">?</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') }}">></abbr> ';
|
||||
if(row.installed) result += '<abbr title="{{ lang._('FIB') }}">*</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"
|
||||
|
||||
Reference in New Issue
Block a user