diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/interfaces-vxlan.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/vpp.py | 207 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/31-to-32 | 9 | ||||
-rwxr-xr-x | src/op_mode/firewall.py | 147 | ||||
-rwxr-xr-x | src/validators/accel-radius-dictionary | 13 |
5 files changed, 143 insertions, 259 deletions
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index ff8144e74..6bf3227d5 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -34,6 +34,7 @@ from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf from vyos.template import is_ipv6 +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -53,7 +54,7 @@ def get_config(config=None): # VXLAN interfaces are picky and require recreation if certain parameters # change. But a VXLAN interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? - for cli_option in ['parameters', 'external', 'gpe', 'group', 'port', 'remote', + for cli_option in ['parameters', 'gpe', 'group', 'port', 'remote', 'source-address', 'source-interface', 'vni']: if is_node_changed(conf, base + [ifname, cli_option]): vxlan.update({'rebuild_required': {}}) @@ -94,17 +95,17 @@ def verify(vxlan): if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): raise ConfigError('Group, remote, source-address or source-interface must be configured') - if 'vni' not in vxlan and 'external' not in vxlan: - raise ConfigError( - 'Must either configure VXLAN "vni" or use "external" CLI option!') + if 'vni' not in vxlan and dict_search('parameters.external', vxlan) == None: + raise ConfigError('Must either configure VXLAN "vni" or use "external" CLI option!') - if {'external', 'vni'} <= set(vxlan): - raise ConfigError('Can not specify both "external" and "VNI"!') + if dict_search('parameters.external', vxlan): + if 'vni' in vxlan: + raise ConfigError('Can not specify both "external" and "VNI"!') - if {'external', 'other_tunnels'} <= set(vxlan): - other_tunnels = ', '.join(vxlan['other_tunnels']) - raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ - f'CLI option is used. Additional tunnels: {other_tunnels}') + if 'other_tunnels' in vxlan: + other_tunnels = ', '.join(vxlan['other_tunnels']) + raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ + f'CLI option is used. Additional tunnels: {other_tunnels}') if 'gpe' in vxlan and 'external' not in vxlan: raise ConfigError(f'VXLAN-GPE is only supported when "external" '\ @@ -164,6 +165,11 @@ def verify(vxlan): raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') vnis_used.append(vni) + if dict_search('parameters.neighbor_suppress', vxlan): + if 'is_bridge_member' not in vxlan: + raise ConfigError('Neighbor suppression requires that VXLAN interface '\ + 'is member of a bridge interface!') + verify_mtu_ipv6(vxlan) verify_address(vxlan) verify_bond_bridge_member(vxlan) diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py deleted file mode 100755 index 82c2f236e..000000000 --- a/src/conf_mode/vpp.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2023 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 psutil import virtual_memory - -from pathlib import Path -from re import search as re_search, MULTILINE as re_M - -from vyos.config import Config -from vyos.configdep import set_dependents, call_dependents -from vyos.configdict import node_changed -from vyos.ifconfig import Section -from vyos.utils.boot import boot_configuration_complete -from vyos.utils.process import call -from vyos.utils.process import rc_cmd -from vyos.utils.system import sysctl_read -from vyos.utils.system import sysctl_apply -from vyos.template import render - -from vyos import ConfigError -from vyos import airbag -from vyos.vpp import VPPControl -from vyos.vpp import HostControl - -airbag.enable() - -service_name = 'vpp' -service_conf = Path(f'/run/vpp/{service_name}.conf') -systemd_override = '/run/systemd/system/vpp.service.d/10-override.conf' - -# Free memory required for VPP -# 2 GB for hugepages + 1 GB for other services -MIN_AVAILABLE_MEMORY: int = 3 * 1024**3 - - -def _get_pci_address_by_interface(iface) -> str: - rc, out = rc_cmd(f'ethtool -i {iface}') - # if ethtool command was successful - if rc == 0 and out: - regex_filter = r'^bus-info: (?P<address>\w+:\w+:\w+\.\w+)$' - re_obj = re_search(regex_filter, out, re_M) - # if bus-info with PCI address found - if re_obj: - address = re_obj.groupdict().get('address', '') - return address - # use VPP - maybe interface already attached to it - vpp_control = VPPControl(attempts=20, interval=500) - pci_addr = vpp_control.get_pci_addr(iface) - if pci_addr: - return pci_addr - # raise error if PCI address was not found - raise ConfigError(f'Cannot find PCI address for interface {iface}') - - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base = ['vpp'] - base_ethernet = ['interfaces', 'ethernet'] - - # find interfaces removed from VPP - removed_ifaces = [] - tmp = node_changed(conf, base + ['interface']) - if tmp: - for removed_iface in tmp: - pci_address: str = _get_pci_address_by_interface(removed_iface) - removed_ifaces.append({ - 'iface_name': removed_iface, - 'iface_pci_addr': pci_address - }) - # add an interface to a list of interfaces that need - # to be reinitialized after the commit - set_dependents('ethernet', conf, removed_iface) - - if not conf.exists(base): - return {'removed_ifaces': removed_ifaces} - - config = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True, - with_recursive_defaults=True) - - if 'interface' in config: - for iface, iface_config in config['interface'].items(): - # add an interface to a list of interfaces that need - # to be reinitialized after the commit - set_dependents('ethernet', conf, iface) - - # Get PCI address auto - if iface_config['pci'] == 'auto': - config['interface'][iface]['pci'] = _get_pci_address_by_interface(iface) - - config['other_interfaces'] = conf.get_config_dict(base_ethernet, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - if removed_ifaces: - config['removed_ifaces'] = removed_ifaces - - return config - - -def verify(config): - # bail out early - looks like removal from running config - if not config or (len(config) == 1 and 'removed_ifaces' in config): - return None - - if 'interface' not in config: - raise ConfigError('"interface" is required but not set!') - - if 'cpu' in config: - if 'corelist_workers' in config['cpu'] and 'main_core' not in config[ - 'cpu']: - raise ConfigError('"cpu main-core" is required but not set!') - - memory_available: int = virtual_memory().available - if memory_available < MIN_AVAILABLE_MEMORY: - raise ConfigError( - 'Not enough free memory to start VPP:\n' - f'available: {round(memory_available / 1024**3, 1)}GB\n' - f'required: {round(MIN_AVAILABLE_MEMORY / 1024**3, 1)}GB') - - -def generate(config): - if not config or (len(config) == 1 and 'removed_ifaces' in config): - # Remove old config and return - service_conf.unlink(missing_ok=True) - return None - - render(service_conf, 'vpp/startup.conf.j2', config) - render(systemd_override, 'vpp/override.conf.j2', config) - - # apply default sysctl values from - # https://github.com/FDio/vpp/blob/v23.06/src/vpp/conf/80-vpp.conf - sysctl_config: dict[str, str] = { - 'vm.nr_hugepages': '1024', - 'vm.max_map_count': '3096', - 'vm.hugetlb_shm_group': '0', - 'kernel.shmmax': '2147483648' - } - # we do not want to reduce `kernel.shmmax` - kernel_shmnax_current: str = sysctl_read('kernel.shmmax') - if int(kernel_shmnax_current) > int(sysctl_config['kernel.shmmax']): - sysctl_config['kernel.shmmax'] = kernel_shmnax_current - - if not sysctl_apply(sysctl_config): - raise ConfigError('Cannot configure sysctl parameters for VPP') - - return None - - -def apply(config): - if not config or (len(config) == 1 and 'removed_ifaces' in config): - call(f'systemctl stop {service_name}.service') - else: - call('systemctl daemon-reload') - call(f'systemctl restart {service_name}.service') - - # Initialize interfaces removed from VPP - for iface in config.get('removed_ifaces', []): - host_control = HostControl() - # rescan PCI to use a proper driver - host_control.pci_rescan(iface['iface_pci_addr']) - # rename to the proper name - iface_new_name: str = host_control.get_eth_name(iface['iface_pci_addr']) - host_control.rename_iface(iface_new_name, iface['iface_name']) - - if 'interface' in config: - # connect to VPP - # must be performed multiple attempts because API is not available - # immediately after the service restart - vpp_control = VPPControl(attempts=20, interval=500) - for iface, _ in config['interface'].items(): - # Create lcp - if iface not in Section.interfaces(): - vpp_control.lcp_pair_add(iface, iface) - - # reinitialize interfaces, but not during the first boot - if boot_configuration_complete(): - call_dependents() - - -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/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32 index 35b397c39..ca3d19320 100755 --- a/src/migration-scripts/interfaces/31-to-32 +++ b/src/migration-scripts/interfaces/31-to-32 @@ -27,7 +27,8 @@ if len(argv) < 2: file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() - base = ['interfaces', 'vxlan'] + +base = ['interfaces', 'vxlan'] config = ConfigTree(config_file) if not config.exists(base): @@ -35,7 +36,11 @@ if not config.exists(base): exit(0) for vxlan in config.list_nodes(base): - if not config.exists(base + ['port']): + if config.exists(base + [vxlan, 'external']): + config.delete(base + [vxlan, 'external']) + config.set(base + [vxlan, 'parameters', 'external']) + + if not config.exists(base + [vxlan, 'port']): config.set(base + [vxlan, 'port'], value='8472') try: diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 3434707ec..20f54b9ba 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2023 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 @@ -24,19 +24,28 @@ from vyos.config import Config from vyos.utils.process import cmd from vyos.utils.dict import dict_search_args -def get_config_firewall(conf, family=None, hook=None, priority=None): - config_path = ['firewall'] - if family: - config_path += [family] - if hook: - config_path += [hook] - if priority: - config_path += [priority] +def get_config_node(conf, node=None, family=None, hook=None, priority=None): + if node == 'nat': + if family == 'ipv6': + config_path = ['nat66'] + else: + config_path = ['nat'] - firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'), + elif node == 'policy': + config_path = ['policy'] + else: + config_path = ['firewall'] + if family: + config_path += [family] + if hook: + config_path += [hook] + if priority: + config_path += [priority] + + node_config = conf.get_config_dict(config_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - return firewall + return node_config def get_nftables_details(family, hook, priority): if family == 'ipv6': @@ -102,7 +111,15 @@ def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=N row.append(rule_details['conditions']) rows.append(row) - if 'default_action' in firewall_conf and not single_rule_id: + if hook in ['input', 'forward', 'output']: + def_action = firewall_conf['default_action'] if 'default_action' in firewall_conf else 'accept' + row = ['default', def_action, 'all'] + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) + rows.append(row) + + elif 'default_action' in firewall_conf and not single_rule_id: row = ['default', firewall_conf['default_action'], 'all'] if 'default-action' in details: rule_details = details['default-action'] @@ -167,16 +184,16 @@ def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule dest_addr = 'any' # Get inbound interface - iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_name') + iiface = dict_search_args(rule_conf, 'inbound_interface', 'name') if not iiface: - iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + iiface = dict_search_args(rule_conf, 'inbound_interface', 'group') if not iiface: iiface = 'any' # Get outbound interface - oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_name') + oiface = dict_search_args(rule_conf, 'outbound_interface', 'name') if not oiface: - oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + oiface = dict_search_args(rule_conf, 'outbound_interface', 'group') if not oiface: oiface = 'any' @@ -198,8 +215,9 @@ def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule if hook in ['input', 'forward', 'output']: row = ['default'] - row.append('N/A') - row.append('N/A') + rule_details = details['default-action'] + row.append(rule_details.get('packets', 0)) + row.append(rule_details.get('bytes', 0)) if 'default_action' in prior_conf: row.append(prior_conf['default_action']) else: @@ -234,7 +252,7 @@ def show_firewall(): print('Rulesets Information') conf = Config() - firewall = get_config_firewall(conf) + firewall = get_config_node(conf) if not firewall: return @@ -249,7 +267,7 @@ def show_firewall_family(family): print(f'Rulesets {family} Information') conf = Config() - firewall = get_config_firewall(conf) + firewall = get_config_node(conf) if not firewall or family not in firewall: return @@ -262,7 +280,7 @@ def show_firewall_name(family, hook, priority): print('Ruleset Information') conf = Config() - firewall = get_config_firewall(conf, family, hook, priority) + firewall = get_config_node(conf, 'firewall', family, hook, priority) if firewall: output_firewall_name(family, hook, priority, firewall) @@ -270,17 +288,20 @@ def show_firewall_rule(family, hook, priority, rule_id): print('Rule Information') conf = Config() - firewall = get_config_firewall(conf, family, hook, priority) + firewall = get_config_node(conf, 'firewall', family, hook, priority) if firewall: output_firewall_name(family, hook, priority, firewall, rule_id) def show_firewall_group(name=None): conf = Config() - firewall = get_config_firewall(conf) + firewall = get_config_node(conf, node='firewall') if 'group' not in firewall: return + nat = get_config_node(conf, node='nat') + policy = get_config_node(conf, node='policy') + def find_references(group_type, group_name): out = [] family = [] @@ -296,6 +317,7 @@ def show_firewall_group(name=None): family = ['ipv4', 'ipv6'] for item in family: + # Look references in firewall for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']: if item in firewall: if name_type not in firewall[item]: @@ -308,8 +330,8 @@ def show_firewall_group(name=None): for rule_id, rule_conf in priority_conf['rule'].items(): source_group = dict_search_args(rule_conf, 'source', 'group', group_type) dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) - in_interface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') - out_interface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group') if source_group: if source_group[0] == "!": source_group = source_group[1:] @@ -330,6 +352,76 @@ def show_firewall_group(name=None): out_interface = out_interface[1:] if group_name == out_interface: out.append(f'{item}-{name_type}-{priority}-{rule_id}') + + # Look references in route | route6 + for name_type in ['route', 'route6']: + if name_type not in policy: + continue + if name_type == 'route' and item == 'ipv6': + continue + elif name_type == 'route6' and item == 'ipv4': + continue + else: + for policy_name, policy_conf in policy[name_type].items(): + if 'rule' not in policy_conf: + continue + for rule_id, rule_conf in policy_conf['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'{name_type}-{policy_name}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'{name_type}-{policy_name}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'{name_type}-{policy_name}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'{name_type}-{policy_name}-{rule_id}') + + ## Look references in nat table + for direction in ['source', 'destination']: + if direction in nat: + if 'rule' not in nat[direction]: + continue + for rule_id, rule_conf in nat[direction]['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'nat-{direction}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'nat-{direction}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'nat-{direction}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'nat-{direction}-{rule_id}') + return out header = ['Name', 'Type', 'References', 'Members'] @@ -356,6 +448,7 @@ def show_firewall_group(name=None): row.append('N/D') rows.append(row) + if rows: print('Firewall Groups\n') print(tabulate.tabulate(rows, header)) @@ -364,7 +457,7 @@ def show_summary(): print('Ruleset Summary') conf = Config() - firewall = get_config_firewall(conf) + firewall = get_config_node(conf) if not firewall: return @@ -410,7 +503,7 @@ def show_statistics(): print('Rulesets Statistics') conf = Config() - firewall = get_config_firewall(conf) + firewall = get_config_node(conf) if not firewall: return diff --git a/src/validators/accel-radius-dictionary b/src/validators/accel-radius-dictionary deleted file mode 100755 index 05287e770..000000000 --- a/src/validators/accel-radius-dictionary +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -DICT_PATH=/usr/share/accel-ppp/radius -NAME=$1 - -if [ -n "$NAME" -a -e $DICT_PATH/dictionary.$NAME ]; then - exit 0 -else - echo "$NAME is not a valid RADIUS dictionary name" - echo "Please make sure that $DICT_PATH/dictionary.$NAME file exists" - exit 1 -fi - |