mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
FRR: OSPF ipv6 influence interface cost via carp (#2929)
add event handler for ospf6 carp demotion including required interface fields (carp_depend_on and cost_demoted). while here polish some small usability issues, knowing: o Interface networktype and interfacename should be single dropdown boxes o diagnostics / bgp - fix search in grid, should only use a formatter for presentation purposes. o carp_frr_ospf6 rc.carp_service_status.d event handler o add "CARP demote" to model, form and template o bugfix previous, missing IFS= (internal field seperator) o missing ospf6d in log event handler
This commit is contained in:
@@ -11,6 +11,13 @@ switching and routing, Internet access routers, and Internet peering.
|
||||
Plugin Changelog
|
||||
================
|
||||
|
||||
1.28
|
||||
|
||||
* OSPF6 Interface change networktype and interfacename to dropdown in stead of multi select
|
||||
* Diagnostics / BGP - fix search in grid, should only use a formatter for presentation purposes.
|
||||
* Add ospf6 carp demotion event handler
|
||||
|
||||
|
||||
1.27
|
||||
|
||||
* Add BGP password authentication
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
if [ -f /etc/rc.conf.d/frr ]; then
|
||||
. /etc/rc.conf.d/frr
|
||||
fi
|
||||
|
||||
if [ "$frr_enable" == "YES" ] && (`echo "$frr_daemons" | /usr/bin/grep -F -q -w "ospf6d"`) &&
|
||||
(`echo "$frr_carp_demote" | /usr/bin/grep -F -q -w "ospf6d"`) ; then
|
||||
# OSPF enabled
|
||||
OSPF_NEIGHBOR=`echo "show ipv6 ospf6 neighbor" | /usr/local/bin/vtysh 2>&1` IFS=
|
||||
if [ "$?" -eq 0 ]; then
|
||||
# running, check if we can find any neighbors
|
||||
IFS=
|
||||
neighbors_count=`echo $OSPF_NEIGHBOR | grep "Full/" | wc -l`
|
||||
unset IFS
|
||||
if [ "$neighbors_count" -eq 0 ]; then
|
||||
# no neighbors in state Full/* found
|
||||
exit 2
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
# not running
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
+14
-2
@@ -7,7 +7,7 @@
|
||||
<field>
|
||||
<id>interface.interfacename</id>
|
||||
<label>Interface</label>
|
||||
<type>select_multiple</type>
|
||||
<type>dropdown</type>
|
||||
<help>Select an interface where this settings apply to.</help>
|
||||
</field>
|
||||
<field>
|
||||
@@ -26,6 +26,18 @@
|
||||
<label>Cost</label>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>interface.cost_demoted</id>
|
||||
<label>Cost (when demoted)</label>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>interface.carp_depend_on</id>
|
||||
<label>Depend on (carp)</label>
|
||||
<type>dropdown</type>
|
||||
<help>The carp VHID to depend on, when this virtual address is not in master state,
|
||||
the interface cost will be set to the demoted cost (specified above).</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>interface.hellointerval</id>
|
||||
<label>Hello Interval</label>
|
||||
@@ -54,6 +66,6 @@
|
||||
<field>
|
||||
<id>interface.networktype</id>
|
||||
<label>Network Type</label>
|
||||
<type>select_multiple</type>
|
||||
<type>dropdown</type>
|
||||
</field>
|
||||
</form>
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
<type>checkbox</type>
|
||||
<help>This will activate the OSPFv3 service if routing protocols are enabled in "General".</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ospf6.carp_demote</id>
|
||||
<label>CARP demote</label>
|
||||
<type>checkbox</type>
|
||||
<help>
|
||||
Register CARP status monitor, when no neighbors are found, consider this node less attractive.
|
||||
This feature needs syslog enabled using "Debugging" logging to catch all relevant status events.
|
||||
This option is not compatible with "Enable CARP Failover".
|
||||
</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ospf6.redistribute</id>
|
||||
<label>Route Redistribution</label>
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</enabled>
|
||||
<carp_demote type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</carp_demote>
|
||||
<redistribute type="OptionField">
|
||||
<Required>N</Required>
|
||||
<multiple>Y</multiple>
|
||||
@@ -52,6 +56,17 @@
|
||||
<MaximumValue>4294967295</MaximumValue>
|
||||
<ValidationMessage>Cost must be between 0 and 4294967295.</ValidationMessage>
|
||||
</cost>
|
||||
<cost_demoted type="IntegerField">
|
||||
<default>65535</default>
|
||||
<MinimumValue>1</MinimumValue>
|
||||
<Required>N</Required>
|
||||
<MaximumValue>65535</MaximumValue>
|
||||
<ValidationMessage>Cost must be between 1 and 65535.</ValidationMessage>
|
||||
</cost_demoted>
|
||||
<carp_depend_on type="VirtualIPField">
|
||||
<type>carp</type>
|
||||
<Required>N</Required>
|
||||
</carp_depend_on>
|
||||
<hellointerval type="IntegerField">
|
||||
<default></default>
|
||||
<MinimumValue>0</MinimumValue>
|
||||
|
||||
@@ -101,7 +101,6 @@ $(document).ready(function() {
|
||||
content = _.template($('#routestpl').html())(data['response']);
|
||||
$('#routing').html(content);
|
||||
$('#routing table').bootgrid({
|
||||
converters: dataconverters,
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
@@ -110,7 +109,6 @@ $(document).ready(function() {
|
||||
content = _.template($('#routestpl').html())(data['response']);
|
||||
$('#routing6').html(content);
|
||||
$('#routing6 table').bootgrid({
|
||||
converters: dataconverters,
|
||||
formatters: dataformatters
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Copyright (c) 2022 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 os
|
||||
import syslog
|
||||
from configparser import ConfigParser
|
||||
from ..base import BaseEventHandler
|
||||
|
||||
|
||||
class Ospf6dEventHandler(BaseEventHandler):
|
||||
_config = '/usr/local/etc/frr/ospf6d_carp.conf'
|
||||
|
||||
@property
|
||||
def should_run(self):
|
||||
return self.vtysh.is_running('ospf6d')
|
||||
|
||||
def _read_config(self):
|
||||
result = dict()
|
||||
if os.path.isfile(self._config):
|
||||
cnf = ConfigParser()
|
||||
cnf.read(self._config)
|
||||
not_empty = lambda x, y: cnf.has_option(x, y) and cnf.get(x, y) != '' and cnf.get(x, y) != '0'
|
||||
for section in cnf.sections():
|
||||
if not_empty(section, 'interface') and not_empty(section, 'interface') \
|
||||
and not_empty(section, 'demoted_cost') and not_empty(section, 'carp_depend_on'):
|
||||
default_cost = cnf.getint(section, 'default_cost') if not_empty(section, 'default_cost') else None
|
||||
result[cnf.get(section, 'interface')] = {
|
||||
'demoted_cost': cnf.getint(section, 'demoted_cost'),
|
||||
'carp_depend_on': cnf.get(section, 'carp_depend_on'),
|
||||
'default_cost': default_cost,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def execute(self):
|
||||
if os.path.isfile(self._config):
|
||||
# parse ospf6 interface data, keep structure similar to what ospf offers when using json output
|
||||
ospf_interfaces = {
|
||||
'interfaces': {}
|
||||
}
|
||||
this_interface = None
|
||||
for line in self.vtysh.execute('show ipv6 ospf6 interface', translate=None).decode().split('\n'):
|
||||
if len(line) > 0 and line[0] != ' ':
|
||||
this_interface = line.split()[0]
|
||||
ospf_interfaces['interfaces'][this_interface] = {}
|
||||
elif this_interface is not None:
|
||||
if line.find('Area ID') > 0 and line.split()[-1].isdigit():
|
||||
# Area ID X.X.X.X, Cost XXXX
|
||||
ospf_interfaces['interfaces'][this_interface]['cost'] = int(line.split()[-1])
|
||||
|
||||
config_interfaces = self._read_config()
|
||||
for intf in config_interfaces:
|
||||
if 'interfaces' in ospf_interfaces and intf in ospf_interfaces['interfaces']:
|
||||
ospf_intf_cost = ospf_interfaces['interfaces'][intf]['cost']
|
||||
is_intf_master = self.ifstatus.address_status(config_interfaces[intf]['carp_depend_on']) == 'master'
|
||||
is_ospf_dem = ospf_intf_cost == config_interfaces[intf]['demoted_cost']
|
||||
if is_intf_master and is_ospf_dem:
|
||||
# promote ospf6 interface
|
||||
conf_cost = config_interfaces[intf]['default_cost']
|
||||
if conf_cost is None:
|
||||
syslog.syslog(
|
||||
syslog.LOG_NOTICE, 'ospf6d promote interface %s (no default cost configured).' % intf
|
||||
)
|
||||
self.vtysh.execute(
|
||||
['interface %s' % intf, 'no ipv6 ospf6 cost'], translate=None, configure=True
|
||||
)
|
||||
elif conf_cost != ospf_intf_cost:
|
||||
syslog.syslog(
|
||||
syslog.LOG_NOTICE, 'ospf6d promote interface %s (cost %d).' % (intf, conf_cost)
|
||||
)
|
||||
self.vtysh.execute(
|
||||
['interface %s' % intf, 'ipv6 ospf6 cost %d' % conf_cost],
|
||||
translate=None, configure=True
|
||||
)
|
||||
elif not is_intf_master and not is_ospf_dem:
|
||||
# demote ospf6 interface
|
||||
conf_cost = config_interfaces[intf]['demoted_cost']
|
||||
syslog.syslog(
|
||||
syslog.LOG_NOTICE, 'ospf6d demote interface %s (cost %d).' % (intf, conf_cost)
|
||||
)
|
||||
self.vtysh.execute(
|
||||
['interface %s' % intf, 'ipv6 ospf6 cost %d' % conf_cost],
|
||||
translate=None, configure=True
|
||||
)
|
||||
@@ -59,8 +59,6 @@ class OspfdEventHandler(BaseEventHandler):
|
||||
if os.path.isfile(self._config):
|
||||
ospf_interfaces = self.vtysh.execute('show ip ospf interface json')
|
||||
config_interfaces = self._read_config()
|
||||
cnf = ConfigParser()
|
||||
cnf.read(self._config)
|
||||
for intf in config_interfaces:
|
||||
if 'interfaces' in ospf_interfaces and intf in ospf_interfaces['interfaces']:
|
||||
ospf_intf_cost = ospf_interfaces['interfaces'][intf]['cost']
|
||||
|
||||
@@ -3,6 +3,7 @@ bgpd.conf:/usr/local/etc/frr/bgpd.conf
|
||||
ospfd.conf:/usr/local/etc/frr/ospfd.conf
|
||||
ospfd_carp.conf:/usr/local/etc/frr/ospfd_carp.conf
|
||||
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
|
||||
frr:/etc/rc.conf.d/frr
|
||||
|
||||
@@ -12,7 +12,10 @@ if helpers.exists('OPNsense.quagga.bgp.enabled') and OPNsense.quagga.bgp.enabled
|
||||
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.isis.enabled') and OPNsense.quagga.isis.enabled == '1' %} isisd{% endif %}"
|
||||
frr_carp_demote="{% if not helpers.empty('OPNsense.quagga.ospf.carp_demote') %} ospfd{% endif %}"
|
||||
frr_carp_demote="{%
|
||||
if not helpers.empty('OPNsense.quagga.ospf.carp_demote') %} ospfd{% endif %}{%
|
||||
if not helpers.empty('OPNsense.quagga.ospf6.carp_demote') %} ospf6d{% endif
|
||||
%}"
|
||||
start_postcmd="/usr/local/opnsense/scripts/frr/carp_event_handler"
|
||||
{% else %}
|
||||
frr_enable="NO"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{% from 'OPNsense/Macros/interface.macro' import physical_interface %}
|
||||
{% if helpers.exists('OPNsense.quagga.ospf6.interfaces.interface') %}
|
||||
{% for interface in helpers.toList('OPNsense.quagga.ospf6.interfaces.interface') %}
|
||||
{% if interface.enabled == '1' %}
|
||||
[{{ interface['@uuid'] }}]
|
||||
enabled={{interface.enabled|default('0')}}
|
||||
interface={{physical_interface(interface.interfacename)}}
|
||||
default_cost={{interface.cost|default('')}}
|
||||
demoted_cost={{interface.cost_demoted|default('')}}
|
||||
carp_depend_on={{interface.carp_depend_on|default('')}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -6,7 +6,8 @@ destination d_frr_event {
|
||||
};
|
||||
|
||||
filter f_frr_ospf {
|
||||
program("ospfd") and (
|
||||
(program("ospfd") or program("ospf6d"))
|
||||
and (
|
||||
(
|
||||
level("info") or level("notice")
|
||||
) or (
|
||||
|
||||
Reference in New Issue
Block a user