From 8d45144ad4662387e2bca30cd4a2f42ade4e0c6b Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Thu, 21 Apr 2022 11:33:59 +0200 Subject: [PATCH] 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 --- net/frr/pkg-descr | 7 ++ .../rc.carp_service_status.d/carp_frr_ospf6 | 25 ++++ .../Quagga/forms/dialogEditOSPF6Interface.xml | 16 ++- .../OPNsense/Quagga/forms/ospf6.xml | 10 ++ .../mvc/app/models/OPNsense/Quagga/OSPF6.xml | 15 +++ .../views/OPNsense/Quagga/diagnosticsbgp.volt | 2 - .../opnsense/scripts/frr/lib/events/ospf6d.py | 107 ++++++++++++++++++ .../opnsense/scripts/frr/lib/events/ospfd.py | 2 - .../templates/OPNsense/Quagga/+TARGETS | 1 + .../service/templates/OPNsense/Quagga/frr | 5 +- .../OPNsense/Quagga/ospf6d_carp.conf | 13 +++ .../OPNsense/Quagga/syslog-ng-frr-events.conf | 3 +- 12 files changed, 198 insertions(+), 8 deletions(-) create mode 100755 net/frr/src/etc/rc.carp_service_status.d/carp_frr_ospf6 create mode 100644 net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py create mode 100644 net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf diff --git a/net/frr/pkg-descr b/net/frr/pkg-descr index be8a21162..ff023d830 100644 --- a/net/frr/pkg-descr +++ b/net/frr/pkg-descr @@ -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 diff --git a/net/frr/src/etc/rc.carp_service_status.d/carp_frr_ospf6 b/net/frr/src/etc/rc.carp_service_status.d/carp_frr_ospf6 new file mode 100755 index 000000000..218699f55 --- /dev/null +++ b/net/frr/src/etc/rc.carp_service_status.d/carp_frr_ospf6 @@ -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 diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditOSPF6Interface.xml b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditOSPF6Interface.xml index 545575167..2ddc6d816 100644 --- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditOSPF6Interface.xml +++ b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditOSPF6Interface.xml @@ -7,7 +7,7 @@ interface.interfacename - select_multiple + dropdown Select an interface where this settings apply to. @@ -26,6 +26,18 @@ text + + interface.cost_demoted + + text + + + interface.carp_depend_on + + dropdown + 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). + interface.hellointerval @@ -54,6 +66,6 @@ interface.networktype - select_multiple + dropdown diff --git a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/ospf6.xml b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/ospf6.xml index 7f6139457..43bfb1b11 100644 --- a/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/ospf6.xml +++ b/net/frr/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/ospf6.xml @@ -5,6 +5,16 @@ checkbox This will activate the OSPFv3 service if routing protocols are enabled in "General". + + ospf6.carp_demote + + checkbox + + 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". + + ospf6.redistribute diff --git a/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/OSPF6.xml b/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/OSPF6.xml index 46e433426..b6a9b1b65 100644 --- a/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/OSPF6.xml +++ b/net/frr/src/opnsense/mvc/app/models/OPNsense/Quagga/OSPF6.xml @@ -7,6 +7,10 @@ 0 Y + + 0 + Y + N Y @@ -52,6 +56,17 @@ 4294967295 Cost must be between 0 and 4294967295. + + 65535 + 1 + N + 65535 + Cost must be between 1 and 65535. + + + carp + N + 0 diff --git a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt index 60c7d72f1..b0e196ce0 100644 --- a/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt +++ b/net/frr/src/opnsense/mvc/app/views/OPNsense/Quagga/diagnosticsbgp.volt @@ -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 }); }); diff --git a/net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py b/net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py new file mode 100644 index 000000000..42d86c9d9 --- /dev/null +++ b/net/frr/src/opnsense/scripts/frr/lib/events/ospf6d.py @@ -0,0 +1,107 @@ +""" + Copyright (c) 2022 Ad Schellevis + 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 + ) diff --git a/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py b/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py index d69274708..7eabadb9b 100755 --- a/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py +++ b/net/frr/src/opnsense/scripts/frr/lib/events/ospfd.py @@ -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'] diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS index c2e0542d6..ea22bda2d 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/+TARGETS @@ -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 diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr index 9c8ee8525..2b0b29166 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/frr +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/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" diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf new file mode 100644 index 000000000..af3400036 --- /dev/null +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/ospf6d_carp.conf @@ -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 %} diff --git a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf index 9644313b8..309d47627 100644 --- a/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf +++ b/net/frr/src/opnsense/service/templates/OPNsense/Quagga/syslog-ng-frr-events.conf @@ -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 (