mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
FRR: work in progress carp event handler + ospfd implementation for https://github.com/opnsense/plugins/issues/2091
This commit is contained in:
committed by
Ad Schellevis
parent
b49b29bc9d
commit
f29d8ddf0b
@@ -25,15 +25,21 @@
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
import sys
|
||||
import syslog
|
||||
import lib.events
|
||||
from lib import InterfaceStatus, VtySH
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
syslog.openlog('frr_carp', logoption=syslog.LOG_DAEMON, facility=syslog.LOG_LOCAL1)
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'FRR received carp configuration event.')
|
||||
ifstatus = InterfaceStatus()
|
||||
vtysh = VtySH()
|
||||
if vtysh.is_active:
|
||||
print (ifstatus.address_status('10.111.112.113'))
|
||||
if vtysh.is_running('ospfd'):
|
||||
ospf_interfaces = vtysh.execute('show ip ospf interface json')
|
||||
vtysh.execute(['interface le1', 'ip ospf cost 65535'], translate=None, configure=True)
|
||||
print(ospf_interfaces)
|
||||
for event in lib.events.get_events():
|
||||
event_object = event(ifstatus=ifstatus, vtysh=vtysh)
|
||||
if event_object.should_run:
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'FRR trigger %s event.' % event_object.__class__.__name__)
|
||||
event_object.execute()
|
||||
else:
|
||||
syslog.syslog(syslog.LOG_ERR, 'no frr deamons active.')
|
||||
|
||||
@@ -69,8 +69,8 @@ class VtySH:
|
||||
self.init()
|
||||
|
||||
def init(self):
|
||||
# wait maximum 30 seconds for daemon to startup
|
||||
for i in range(30):
|
||||
# wait a maximum of 5 seconds for daemon to startup
|
||||
for i in range(5):
|
||||
try:
|
||||
self._daemons = self.execute('show daemons', lambda x: x.decode().split())
|
||||
break
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
"""
|
||||
|
||||
class baseEventHandler:
|
||||
class BaseEventHandler:
|
||||
def __init__(self, ifstatus, vtysh):
|
||||
self.ifstatus = ifstatus
|
||||
self.vtysh = vtysh
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
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 glob
|
||||
import importlib
|
||||
import sys
|
||||
import os
|
||||
from ..base import BaseEventHandler
|
||||
|
||||
|
||||
def get_events():
|
||||
""" iterate event handlers
|
||||
"""
|
||||
for filename in glob.glob("%s/*.py" % os.path.dirname(__file__)):
|
||||
importlib.import_module(".%s" % os.path.splitext(os.path.basename(filename))[0], __name__)
|
||||
|
||||
for module_name in dir(sys.modules[__name__]):
|
||||
for attribute_name in dir(getattr(sys.modules[__name__], module_name)):
|
||||
cls = getattr(getattr(sys.modules[__name__], module_name), attribute_name)
|
||||
if isinstance(cls, type) and issubclass(cls, BaseEventHandler) and cls != BaseEventHandler:
|
||||
yield cls
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
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 os
|
||||
import syslog
|
||||
from configparser import ConfigParser
|
||||
from ..base import BaseEventHandler
|
||||
|
||||
|
||||
class OspfdEventHandler(BaseEventHandler):
|
||||
_config = '/usr/local/etc/frr/ospfd_carp.conf'
|
||||
|
||||
@property
|
||||
def should_run(self):
|
||||
return self.vtysh.is_running('ospfd')
|
||||
|
||||
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):
|
||||
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']
|
||||
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 ospf interface
|
||||
conf_cost = config_interfaces[intf]['default_cost']
|
||||
if conf_cost is None:
|
||||
syslog.syslog(
|
||||
syslog.LOG_NOTICE, 'ospfd promote interface %s (no default cost configured).' % intf
|
||||
)
|
||||
self.vtysh.execute(
|
||||
['interface %s' % intf, 'no ip ospf cost'], translate=None, configure=True
|
||||
)
|
||||
elif conf_cost != ospf_intf_cost:
|
||||
syslog.syslog(
|
||||
syslog.LOG_NOTICE, 'ospfd promote interface %s (cost %d).' % (intf, conf_cost)
|
||||
)
|
||||
self.vtysh.execute(
|
||||
['interface %s' % intf, 'ip ospf cost %d' % conf_cost],
|
||||
translate=None, configure=True
|
||||
)
|
||||
elif not is_intf_master and not is_ospf_dem:
|
||||
# demote ospf interface
|
||||
conf_cost = config_interfaces[intf]['demoted_cost']
|
||||
syslog.syslog(
|
||||
syslog.LOG_NOTICE, 'ospfd demote interface %s (cost %d).' % (intf, conf_cost)
|
||||
)
|
||||
self.vtysh.execute(
|
||||
['interface %s' % intf, 'ip ospf cost %d' % conf_cost],
|
||||
translate=None, configure=True
|
||||
)
|
||||
@@ -1,9 +1,11 @@
|
||||
{% from 'OPNsense/Macros/interface.macro' import physical_interface %}
|
||||
{% if helpers.exists('OPNsense.quagga.ospf.interfaces.interface') %}
|
||||
{% for interface in helpers.toList('OPNsense.quagga.ospf.interfaces.interface') %}
|
||||
{% if interface.enabled == '1' %}
|
||||
[interface_interface.interfacename]
|
||||
interface={{interface.interfacename}}
|
||||
default_cost={{interface.cost|default('10')}}
|
||||
[{{ 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 %}
|
||||
|
||||
Reference in New Issue
Block a user