diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/dhcpv6_relay.py | 94 | ||||
-rwxr-xr-x | src/conf_mode/dhcpv6_server.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/nat.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/protocols_igmp.py | 5 | ||||
-rwxr-xr-x | src/conf_mode/protocols_isis.py | 39 | ||||
-rwxr-xr-x | src/conf_mode/protocols_mpls.py | 36 | ||||
-rwxr-xr-x | src/conf_mode/protocols_pim.py | 5 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 67 | ||||
-rwxr-xr-x | src/op_mode/lldp_op.py | 96 | ||||
-rw-r--r-- | src/systemd/isc-dhcp-relay6.service | 8 |
10 files changed, 210 insertions, 144 deletions
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index d4212b8be..cf8a26674 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -17,90 +17,84 @@ import os from sys import exit -from copy import deepcopy from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge +from vyos.ifconfig import Interface from vyos.template import render - +from vyos.util import call +from vyos.util import dict_search +from vyos.validate import is_ipv6_link_local +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/run/dhcp-relay/dhcpv6.conf' - -default_config_data = { - 'listen_addr': [], - 'upstream_addr': [], - 'options': [], -} +config_file = '/run/dhcp-relay/dhcrelay6.conf' def get_config(config=None): - relay = deepcopy(default_config_data) if config: conf = config else: conf = Config() - if not conf.exists('service dhcpv6-relay'): + base = ['service', 'dhcpv6-relay'] + if not conf.exists(base): return None - else: - conf.set_level('service dhcpv6-relay') - - # Network interfaces/address to listen on for DHCPv6 query(s) - if conf.exists('listen-interface'): - interfaces = conf.list_nodes('listen-interface') - for intf in interfaces: - if conf.exists('listen-interface {0} address'.format(intf)): - addr = conf.return_value('listen-interface {0} address'.format(intf)) - listen = addr + '%' + intf - relay['listen_addr'].append(listen) - - # Upstream interface/address for remote DHCPv6 server - if conf.exists('upstream-interface'): - interfaces = conf.list_nodes('upstream-interface') - for intf in interfaces: - addresses = conf.return_values('upstream-interface {0} address'.format(intf)) - for addr in addresses: - server = addr + '%' + intf - relay['upstream_addr'].append(server) - - # Maximum hop count. When forwarding packets, dhcrelay discards packets - # which have reached a hop count of COUNT. Default is 10. Maximum is 255. - if conf.exists('max-hop-count'): - count = '-c ' + conf.return_value('max-hop-count') - relay['options'].append(count) - - if conf.exists('use-interface-id-option'): - relay['options'].append('-I') + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + relay = dict_merge(default_values, relay) return relay def verify(relay): # bail out early - looks like removal from running config - if relay is None: + if not relay: return None - if len(relay['listen_addr']) == 0 or len(relay['upstream_addr']) == 0: - raise ConfigError('Must set at least one listen and upstream interface addresses.') + if 'upstream_interface' not in relay: + raise ConfigError('At least one upstream interface required!') + for interface, config in relay['upstream_interface'].items(): + if 'address' not in config: + raise ConfigError('DHCPv6 server required for upstream ' \ + f'interface {interface}!') + + if 'listen_interface' not in relay: + raise ConfigError('At least one listen interface required!') + + # DHCPv6 relay requires at least one global unicat address assigned to the + # interface + for interface in relay['listen_interface']: + has_global = False + for addr in Interface(interface).get_addr(): + if not is_ipv6_link_local(addr.split('/')[0]): + has_global = True + if not has_global: + raise ConfigError(f'Interface {interface} does not have global '\ + 'IPv6 address assigned!') return None def generate(relay): # bail out early - looks like removal from running config - if relay is None: + if not relay: return None - render(config_file, 'dhcpv6-relay/config.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay) return None def apply(relay): - if relay is not None: - call('systemctl restart isc-dhcp-relay6.service') - else: + # bail out early - looks like removal from running config + if not relay: # DHCPv6 relay support is removed in the commit call('systemctl stop isc-dhcp-relay6.service') if os.path.exists(config_file): os.unlink(config_file) + return None + + call('systemctl restart isc-dhcp-relay6.service') return None diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index c2868e078..db248de50 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -390,7 +390,7 @@ def generate(dhcpv6): if not dhcpv6 or dhcpv6['disabled']: return None - render(config_file, 'dhcpv6-server/dhcpdv6.conf.tmpl', dhcpv6) + render(config_file, 'dhcp-server/dhcpdv6.conf.tmpl', dhcpv6) return None def apply(dhcpv6): diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index f5c023b81..1ccec3d2e 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -130,7 +130,7 @@ def verify(nat): # no need to verify the CLI as NAT is going to be deactivated return None - if nat['helper_functions']: + if 'helper_functions' in nat: if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): raise Exception('could not determine nftable ruleset handlers') diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index 8606e7364..28d560d03 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -28,6 +28,9 @@ from signal import SIGTERM from vyos import airbag airbag.enable() +# Required to use the full path to pimd, in another case daemon will not be started +pimd_cmd = f'/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1' + config_file = r'/tmp/igmp.frr' def get_config(config=None): @@ -115,7 +118,7 @@ def apply(igmp): pim_pid = process_named_running('pimd') if igmp['igmp_conf'] or igmp['pim_conf']: if not pim_pid: - call(f'pimd -d -F traditional --daemon -A 127.0.0.1') + call(pimd_cmd) if os.path.exists(config_file): call(f'vtysh -d pimd -f {config_file}') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index df03fd990..97ab79583 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2017-2020 VyOS maintainers and contributors +# Copyright (C) 2020 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -28,8 +28,6 @@ from vyos import frr from vyos import airbag airbag.enable() -config_file = r'/tmp/isis.frr' - def get_config(config=None): if config: conf = config @@ -39,13 +37,6 @@ def get_config(config=None): isis = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # determine which members have been removed - for instance in isis: - conf.set_level(base + [instance]) - tmp = node_changed(conf, ['interface']) - if tmp: - isis[instance].update({'interface_remove': tmp}) - return isis def verify(isis): @@ -106,9 +97,6 @@ def generate(isis): process = list(isis.keys())[0] isis[process]['process'] = process - # render(config) not needed, its only for debug - render(config_file, 'frr/isis.frr.tmpl', isis[process]) - isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', isis[process]) @@ -116,24 +104,27 @@ def generate(isis): def apply(isis): # Save original configuration prior to starting any commit actions - frr_cfg = {} - frr_cfg['original_config'] = frr.get_configuration(daemon='isisd') - frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], isis['new_frr_config'], from_re='router isis .*') + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(daemon='isisd') + frr_cfg.modify_section(r'interface \S+', '') + frr_cfg.modify_section(f'router isis \S+', '') + frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', isis['new_frr_config']) + frr_cfg.commit_configuration(daemon='isisd') + + # If FRR config is blank, rerun the blank commit x times due to frr-reload + # behavior/bug not properly clearing out on one commit. + if isis['new_frr_config'] == '': + for a in range(5): + frr_cfg.commit_configuration(daemon='isisd') # Debugging + ''' print('') print('--------- DEBUGGING ----------') print(f'Existing config:\n{frr_cfg["original_config"]}\n\n') print(f'Replacement config:\n{isis["new_frr_config"]}\n\n') print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n') - - # FRR mark configuration will test for syntax errors and throws an - # exception if any syntax errors is detected - frr.mark_configuration(frr_cfg['modified_config']) - - # Commit resulting configuration to FRR, this will throw CommitError - # on failure - frr.reload_configuration(frr_cfg['modified_config'], daemon='isisd') + ''' return None diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 791b18110..3b27608da 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -18,11 +18,13 @@ import os from sys import exit +from glob import glob from vyos.config import Config from vyos.configdict import node_changed from vyos.template import render_to_string from vyos.util import call from vyos.util import dict_search +from vyos.util import read_file from vyos import ConfigError from vyos import frr from vyos import airbag @@ -118,22 +120,28 @@ def apply(mpls): # Enable and disable MPLS processing on interfaces per configuration if 'interface' in mpls: system_interfaces = [] - system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).split('\n'))) - del system_interfaces[0][-1] - for configured_interface in mpls['interface']: - for system_interface in system_interfaces[0]: - if configured_interface in system_interface: - call(f'sysctl -wq net.mpls.conf.{configured_interface}.input=1') - elif system_interface.endswith(' = 1'): - system_interface = system_interface.replace(' = 1', '=0') - call(f'sysctl -wq {system_interface}') + # Populate system interfaces list with local MPLS capable interfaces + for interface in glob('/proc/sys/net/mpls/conf/*'): + system_interfaces.append(os.path.basename(interface)) + # This is where the comparison is done on if an interface needs to be enabled/disabled. + for system_interface in system_interfaces: + interface_state = read_file(f'/proc/sys/net/mpls/conf/{system_interface}/input') + if '1' in interface_state: + if system_interface not in mpls['interface']: + system_interface = system_interface.replace('.', '/') + call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') + elif '0' in interface_state: + if system_interface in mpls['interface']: + system_interface = system_interface.replace('.', '/') + call(f'sysctl -wq net.mpls.conf.{system_interface}.input=1') else: - # If MPLS interfaces are not configured, set MPLS processing disabled system_interfaces = [] - system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).replace(" = 1", "=0")).split('\n')) - del system_interfaces[0][-1] - for interface in (system_interfaces[0]): - call(f'sysctl -wq {interface}') + # If MPLS interfaces are not configured, set MPLS processing disabled + for interface in glob('/proc/sys/net/mpls/conf/*'): + system_interfaces.append(os.path.basename(interface)) + for system_interface in system_interfaces: + system_interface = system_interface.replace('.', '/') + call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') return None diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 8a9f034d5..df2e6f941 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -28,6 +28,9 @@ from signal import SIGTERM from vyos import airbag airbag.enable() +# Required to use the full path to pimd, in another case daemon will not be started +pimd_cmd = f'/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1' + config_file = r'/tmp/pimd.frr' def get_config(config=None): @@ -142,7 +145,7 @@ def apply(pim): pim_pid = process_named_running('pimd') if pim['igmp_conf'] or pim['pim_conf']: if not pim_pid: - call(f'pimd -d -F traditional --daemon -A 127.0.0.1') + call(pimd_cmd) if os.path.exists(config_file): call("vtysh -d pimd -f " + config_file) diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py new file mode 100755 index 000000000..969266c30 --- /dev/null +++ b/src/conf_mode/vpn_ipsec.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.util import call +from vyos.util import dict_search +from vyos import ConfigError +from vyos import airbag +from pprint import pprint +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'nipsec'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return ipsec + +def verify(ipsec): + if not ipsec: + return None + +def generate(ipsec): + if not ipsec: + return None + + return ipsec + +def apply(ipsec): + if not ipsec: + return None + + pprint(ipsec) + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index fa19e7d45..731e71891 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2020 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -45,47 +45,51 @@ Device ID Local Proto Cap Platform Port ID def get_neighbors(): return cmd('/usr/sbin/lldpcli -f json show neighbors') -def parse_data(data): +def parse_data(data, interface): output = [] - for local_if, values in data.items(): - for chassis, c_value in values.get('chassis', {}).items(): - capabilities = c_value['capability'] - if isinstance(capabilities, dict): - capabilities = [capabilities] - - cap = '' - for capability in capabilities: - if capability['enabled']: - if capability['type'] == 'Router': - cap += 'R' - if capability['type'] == 'Bridge': - cap += 'B' - if capability['type'] == 'Wlan': - cap += 'W' - if capability['type'] == 'Station': - cap += 'S' - if capability['type'] == 'Repeater': - cap += 'r' - if capability['type'] == 'Telephone': - cap += 'T' - if capability['type'] == 'Docsis': - cap += 'D' - if capability['type'] == 'Other': - cap += 'O' - - - remote_if = 'Unknown' - if 'descr' in values.get('port', {}): - remote_if = values.get('port', {}).get('descr') - elif 'id' in values.get('port', {}): - remote_if = values.get('port', {}).get('id').get('value', 'Unknown') - - output.append({local_if: {'chassis': chassis, - 'remote_if': remote_if, - 'proto': values.get('via','Unknown'), - 'platform': c_value.get('descr', 'Unknown'), - 'capabilities': cap}}) - + if not isinstance(data, list): + data = [data] + + for neighbor in data: + for local_if, values in neighbor.items(): + if interface is not None and local_if != interface: + continue + for chassis, c_value in values.get('chassis', {}).items(): + capabilities = c_value['capability'] + if isinstance(capabilities, dict): + capabilities = [capabilities] + + cap = '' + for capability in capabilities: + if capability['enabled']: + if capability['type'] == 'Router': + cap += 'R' + if capability['type'] == 'Bridge': + cap += 'B' + if capability['type'] == 'Wlan': + cap += 'W' + if capability['type'] == 'Station': + cap += 'S' + if capability['type'] == 'Repeater': + cap += 'r' + if capability['type'] == 'Telephone': + cap += 'T' + if capability['type'] == 'Docsis': + cap += 'D' + if capability['type'] == 'Other': + cap += 'O' + + remote_if = 'Unknown' + if 'descr' in values.get('port', {}): + remote_if = values.get('port', {}).get('descr') + elif 'id' in values.get('port', {}): + remote_if = values.get('port', {}).get('id').get('value', 'Unknown') + + output.append({local_if: {'chassis': chassis, + 'remote_if': remote_if, + 'proto': values.get('via','Unknown'), + 'platform': c_value.get('descr', 'Unknown'), + 'capabilities': cap}}) output = {'neighbors': output} return output @@ -107,18 +111,14 @@ if __name__ == '__main__': neighbors = dict() if 'interface' in tmp.get('lldp'): - if args.all: - neighbors = tmp['lldp']['interface'] - elif args.interface: - if args.interface in tmp['lldp']['interface']: - neighbors[args.interface] = tmp['lldp']['interface'][args.interface] + neighbors = tmp['lldp']['interface'] else: parser.print_help() exit(1) - tmpl = jinja2.Template(lldp_out) - config_text = tmpl.render(parse_data(neighbors)) + tmpl = jinja2.Template(lldp_out, trim_blocks=True) + config_text = tmpl.render(parse_data(neighbors, interface=args.interface)) print(config_text) exit(0) diff --git a/src/systemd/isc-dhcp-relay6.service b/src/systemd/isc-dhcp-relay6.service index 85ff16e41..30037e013 100644 --- a/src/systemd/isc-dhcp-relay6.service +++ b/src/systemd/isc-dhcp-relay6.service @@ -3,7 +3,7 @@ Description=ISC DHCP IPv6 relay Documentation=man:dhcrelay(8) Wants=network-online.target RequiresMountsFor=/run -ConditionPathExists=/run/dhcp-relay/dhcpv6.conf +ConditionPathExists=/run/dhcp-relay/dhcrelay6.conf After=vyos-router.service [Service] @@ -11,9 +11,9 @@ Type=forking WorkingDirectory=/run/dhcp-relay RuntimeDirectory=dhcp-relay RuntimeDirectoryPreserve=yes -EnvironmentFile=/run/dhcp-relay/dhcpv6.conf -PIDFile=/run/dhcp-relay/dhcrelayv6.pid -ExecStart=/usr/sbin/dhcrelay -6 -pf /run/dhcp-relay/dhcrelayv6.pid $OPTIONS +EnvironmentFile=/run/dhcp-relay/dhcrelay6.conf +PIDFile=/run/dhcp-relay/dhcrelay6.pid +ExecStart=/usr/sbin/dhcrelay -6 -pf /run/dhcp-relay/dhcrelay6.pid $OPTIONS Restart=always [Install] |