Add staticd to routing suite (#3904)

---------

Co-authored-by: Mike Shuey <shuey@fmepnet.org>
This commit is contained in:
Ad Schellevis
2024-04-13 14:56:04 +02:00
committed by GitHub
co-authored by Mike Shuey
parent 5c1ecf0ba5
commit 74c1c75491
12 changed files with 385 additions and 2 deletions
@@ -0,0 +1,69 @@
<?php
/**
* Copyright (C) 2024 Deciso B.V.
* Copyright (C) 2024 Mike Shuey
*
* 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.
*
*/
namespace OPNsense\Quagga\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
class StaticController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'staticd';
protected static $internalModelClass = '\OPNsense\Quagga\STATICd';
public function searchRouteAction()
{
return $this->searchBase('routes.route');
}
public function getRouteAction($uuid = null)
{
return $this->getBase('route', 'routes.route', $uuid);
}
public function setRouteAction($uuid)
{
return $this->setBase('route', 'routes.route', $uuid);
}
public function addRouteAction()
{
return $this->addBase('route', 'routes.route');
}
public function delRouteAction($uuid)
{
return $this->delBase('routes.route', $uuid);
}
public function toggleRouteAction($uuid)
{
return $this->toggleBase('routes.route', $uuid);
}
}
@@ -0,0 +1,41 @@
<?php
/**
* Copyright (C) 2024 Deciso B.V.
* Copyright (C) 2024 Mike Shuey
*
* 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.
*
*/
namespace OPNsense\Quagga;
class StaticController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->staticForm = $this->getForm("static");
$this->view->formDialogEditSTATICRoute = $this->getForm("dialogEditSTATICRoute");
$this->view->pick('OPNsense/Quagga/static');
}
}
@@ -0,0 +1,25 @@
<form>
<field>
<id>route.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
</field>
<field>
<id>route.network</id>
<label>Network</label>
<type>text</type>
<help>Defines the target for the static route, in CIDR notation.</help>
</field>
<field>
<id>route.gateway</id>
<label>Gateway (optional)</label>
<type>text</type>
<help>Optional gateway IP address for this route.</help>
</field>
<field>
<id>route.interfacename</id>
<label>Interface</label>
<type>dropdown</type>
<help>Select an interface where this settings apply to.</help>
</field>
</form>
@@ -0,0 +1,8 @@
<form>
<field>
<id>staticd.enabled</id>
<label>Enable</label>
<type>checkbox</type>
<help>This will activate the staticd service if the support of routing protocols is enabled in "General".</help>
</field>
</form>
@@ -7,7 +7,8 @@
<!--<ISIS VisibleName="IS-IS" cssClass="fa fa-bolt fa-fw" url="/ui/quagga/isis/index" order="30" />-->
<BGP VisibleName="BGP" cssClass="fa fa-globe fa-fw" url="/ui/quagga/bgp/index" order="40" />
<BFD VisibleName="BFD" cssClass="fa fa-exchange fa-fw" url="/ui/quagga/bfd/index" order="50" />
<Diagnostics VisibleName="Diagnostics" cssClass="fa fa-medkit fa-fw" order="50">
<STATIC VisibleName="STATIC" cssClass="fa fa-expand fa-fw" url="/ui/quagga/static/index" order="60" />
<Diagnostics VisibleName="Diagnostics" cssClass="fa fa-medkit fa-fw" order="70">
<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" />
@@ -0,0 +1,69 @@
<?php
/**
* Copyright (C) 2024 Deciso B.V.
* Copyright (C) 2024 Mike Shuey
*
* 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.
*
*/
namespace OPNsense\Quagga;
use OPNsense\Base\Messages\Message;
use OPNsense\Base\BaseModel;
/* For consistency with the other frr models, this model should be named STATIC, which is a reserved keyword */
class STATICd extends BaseModel
{
/**
* {@inheritdoc}
*/
public function performValidation($validateFullModel = false)
{
$messages = parent::performValidation($validateFullModel);
foreach ($this->routes->route->iterateItems() as $route) {
if (!$validateFullModel && !$route->isFieldChanged()) {
continue;
}
$key = $route->__reference;
if (!empty((string)$route->network) && !empty((string)$route->gateway)) {
$net_proto = str_contains($route->network, ':') ? 'inet6' : 'inet';
$gw_proto = str_contains($route->gateway, ':') ? 'inet6' : 'inet';
if ($net_proto != $gw_proto) {
$messages->appendMessage(
new Message(gettext("Gateway IP protocol should match network protocol"), $key . ".gateway")
);
}
}
if (empty((string)$route->gateway) && empty((string)$route->interfacename)) {
$messages->appendMessage(
new Message(
gettext("When no interface is provided, at least a gateway must be offered"),
$key . ".gateway"
)
);
}
}
return $messages;
}
}
@@ -0,0 +1,36 @@
<model>
<mount>//OPNsense/quagga/static</mount>
<description>Staticd Routing configuration</description>
<version>1.0.0</version>
<items>
<enabled type="BooleanField">
<default>0</default>
<Required>Y</Required>
</enabled>
<routes>
<route type="ArrayField">
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<network type="NetworkField">
<NetMaskRequired>Y</NetMaskRequired>
<Required>Y</Required>
<ValidationMessage>Specify a valid network matching the gateways ip protocol.</ValidationMessage>
</network>
<gateway type="NetworkField">
<NetMaskAllowed>N</NetMaskAllowed>
<Required>N</Required>
</gateway>
<interfacename type="InterfaceField">
<multiple>N</multiple>
<AllowDynamic>Y</AllowDynamic>
<filters>
<enable>/^(?!0).*$/</enable>
<type>/^(?!group).*$/</type>
</filters>
</interfacename>
</route>
</routes>
</items>
</model>
@@ -115,7 +115,8 @@ POSSIBILITY OF SUCH DAMAGE.
'connected': {short: 'C', long: '{{ lang._('Connected') }}'},
'bgp': {short: 'B', long: '{{ lang._('BGP') }}'},
'ospf': {short: 'O', long: '{{ lang._('OSPF') }}'},
'ospf6': {short: 'O', long: '{{ lang._('OSPFv3') }}'}
'ospf6': {short: 'O', long: '{{ lang._('OSPFv3') }}'},
'static': {short: 'S', long: '{{ lang._('STATIC') }}'},
};
let field = $("<div/>");
if (protocols[row.protocol] !== undefined) {
@@ -0,0 +1,113 @@
{#
# Copyright (c) 2024 Deciso B.V.
# Copyright (c) 2024 by Mike Shuey
# 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.
#}
<script>
$( document ).ready(function() {
let data_get_map = {'frm_static_settings':"/api/quagga/static/get"};
mapDataToFormUI(data_get_map).done(function(data){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
updateServiceControlUI('quagga');
});
$("#grid-iproutes").UIBootgrid({
'search':'/api/quagga/static/searchRoute',
'get':'/api/quagga/static/getRoute/',
'set':'/api/quagga/static/setRoute/',
'add':'/api/quagga/static/addRoute/',
'del':'/api/quagga/static/delRoute/',
'toggle':'/api/quagga/static/toggleRoute/'
});
$("#reconfigureAct").SimpleActionButton({
onPreAction: function() {
const dfObj = new $.Deferred();
saveFormToEndpoint("/api/quagga/static/set", 'frm_static_settings', function () { dfObj.resolve(); }, true, function () { dfObj.reject(); });
return dfObj;
},
onAction: function(data, status) {
updateServiceControlUI('quagga');
}
});
});
</script>
<!-- Navigation bar -->
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#general">{{ lang._('General') }}</a></li>
<li><a data-toggle="tab" href="#iproute">{{ lang._('Routes') }}</a></li>
</ul>
<div class="tab-content content-box">
<!-- general settings -->
<div id="general" class="tab-pane fade in active">
{{ partial("layout_partials/base_form",['fields':staticForm,'id':'frm_static_settings'])}}
</div>
<!-- Tab: Routes -->
<div id="iproute" class="tab-pane fade in">
<table id="grid-iproutes" class="table table-responsive" data-editDialog="DialogEditSTATICRoute">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="enabled" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="network" data-type="string">{{ lang._('Network') }}</th>
<th data-column-id="gateway" data-type="string">{{ lang._('Gateway') }}</th>
<th data-column-id="interfacename" data-type="string">{{ lang._('Interface') }}</th>
<th data-column-id="commands" data-formatter="commands">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<section class="page-content-main">
<div class="content-box">
<div class="col-md-12">
<br/>
<button class="btn btn-primary" id="reconfigureAct"
data-endpoint='/api/quagga/service/reconfigure'
data-label="{{ lang._('Apply') }}"
data-error-title="{{ lang._('Error reconfiguring STATIC') }}"
type="button"
></button>
<br/><br/>
</div>
</div>
</section>
{{ partial("layout_partials/base_dialog",['fields':formDialogEditSTATICRoute,'id':'DialogEditSTATICRoute','label':lang._('Edit Routes')])}}
@@ -6,6 +6,7 @@ ospf6d.conf:/usr/local/etc/frr/ospf6d.conf
ospf6d_carp.conf:/usr/local/etc/frr/ospf6d_carp.conf
ripd.conf:/usr/local/etc/frr/ripd.conf
sa_policies.conf:/usr/local/etc/frr/sa_policies.conf
staticd.conf:/usr/local/etc/frr/staticd.conf
frr:/etc/rc.conf.d/frr
zebra.conf:/usr/local/etc/frr/zebra.conf
vtysh.conf:/usr/local/etc/frr/vtysh.conf
@@ -13,6 +13,7 @@ if helpers.exists('OPNsense.quagga.bfd.enabled') and OPNsense.quagga.bfd.enabled
if helpers.exists('OPNsense.quagga.bgp.enabled') and OPNsense.quagga.bgp.enabled == '1' %} bgpd{% endif %}{%
if helpers.exists('OPNsense.quagga.ospf6.enabled') and OPNsense.quagga.ospf6.enabled == '1' %} ospf6d{% endif %}{%
if helpers.exists('OPNsense.quagga.ripng.enabled') and OPNsense.quagga.ripng.enabled == '1' %} ripngd{% endif %}{%
if helpers.exists('OPNsense.quagga.staticd.enabled') and OPNsense.quagga.staticd.enabled == '1' %} staticd{% endif %}{%
if helpers.exists('OPNsense.quagga.isis.enabled') and OPNsense.quagga.isis.enabled == '1' %} isisd{% endif %}"
frr_carp_demote="{%
if not helpers.empty('OPNsense.quagga.ospf.carp_demote') %} ospfd{% endif %}{%
@@ -0,0 +1,18 @@
!
! staticd Zebra config autogenerated by OPNsense
!
{% if not helpers.empty('OPNsense.quagga.static.enabled') %}
{% if not helpers.empty('OPNsense.quagga.general') %}
log syslog {{ OPNsense.quagga.general.sysloglevel }}
{% endif %}
{% if not helpers.empty('OPNsense.quagga.general.profile') %}
frr defaults {{ OPNsense.quagga.general.profile }}
{% endif %}
!
{% for route in helpers.toList('OPNsense.quagga.static.routes.route') %}
{% if route.enabled == '1' %}
{% if ':' in route.network %}ipv6{% else %}ip{% endif %} route {{ route.network }} {{ route.gateway|default('')}} {{ helpers.physical_interface(route.interfacename) }}
{% endif %}
{% endfor %}
!
{% endif %}