From a378822f26268c1e8cbfcf754e5cad5c310c7c3c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 27 Jul 2021 18:34:45 +0000 Subject: pbr: T3702: Add rules match fwmark --- src/conf_mode/policy-local-route.py | 39 +++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 013f22665..0b7ceedeb 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 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 @@ -44,17 +44,26 @@ def get_config(config=None): if tmp: for rule in (tmp or []): src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) if src: dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) pbr.update(dict) + if fwmk: + dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + pbr.update(dict) # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x if 'rule' in pbr: for rule in pbr['rule']: src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) if src: dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) pbr.update(dict) + if fwmk: + dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + pbr.update(dict) return pbr @@ -65,8 +74,8 @@ def verify(pbr): if 'rule' in pbr: for rule in pbr['rule']: - if 'source' not in pbr['rule'][rule]: - raise ConfigError('Source address required!') + if 'source' not in pbr['rule'][rule] and 'fwmark' not in pbr['rule'][rule]: + raise ConfigError('Source address or fwmark is required!') else: if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']: raise ConfigError('Table set is required!') @@ -86,16 +95,34 @@ def apply(pbr): # Delete old rule if needed if 'rule_remove' in pbr: for rule in pbr['rule_remove']: - for src in pbr['rule_remove'][rule]['source']: - call(f'ip rule del prio {rule} from {src}') + if 'source' in pbr['rule_remove'][rule]: + for src in pbr['rule_remove'][rule]['source']: + call(f'ip rule del prio {rule} from {src}') + if 'fwmark' in pbr['rule_remove'][rule]: + for fwmk in pbr['rule_remove'][rule]['fwmark']: + call(f'ip rule del prio {rule} from all fwmark {fwmk}') # Generate new config if 'rule' in pbr: for rule in pbr['rule']: table = pbr['rule'][rule]['set']['table'] - if pbr['rule'][rule]['source']: + # Only source in the rule + # set policy local-route rule 100 source '203.0.113.1' + if 'source' in pbr['rule'][rule] and not 'fwmark' in pbr['rule'][rule]: for src in pbr['rule'][rule]['source']: call(f'ip rule add prio {rule} from {src} lookup {table}') + # Only fwmark in the rule + # set policy local-route rule 101 fwmark '23' + if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]: + for fwmk in pbr['rule'][rule]['fwmark']: + call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}') + # Source and fwmark in the rule + # set policy local-route rule 100 source '203.0.113.1' + # set policy local-route rule 100 fwmark '23' + if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]: + for src in pbr['rule'][rule]['source']: + for fwmk in pbr['rule'][rule]['fwmark']: + call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}') return None -- cgit v1.2.3 From ef694deb9a620ab4f81e08ac2bf6228404aaac36 Mon Sep 17 00:00:00 2001 From: Kroy Date: Thu, 12 Aug 2021 18:02:00 -0500 Subject: T3749: Moving some counters into the proper loop --- src/conf_mode/containers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 21b47f42a..7544bd840 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -142,9 +142,9 @@ def verify(container): # Add new network if 'network' in container: - v4_prefix = 0 - v6_prefix = 0 for network, network_config in container['network'].items(): + v4_prefix = 0 + v6_prefix = 0 # If ipv4-prefix not defined for user-defined network if 'prefix' not in network_config: raise ConfigError(f'prefix for network "{net}" must be defined!') -- cgit v1.2.3 From de88a17ba97200cdb3bd07ac3d12eab5d5fa6c73 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 11 Aug 2021 17:21:01 +0000 Subject: isis: T3708: Fix errors in MTU calculation --- src/conf_mode/protocols_isis.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index d4c82249b..4cf0312e9 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -113,9 +113,13 @@ def verify(isis): # Interface MTU must be >= configured lsp-mtu mtu = Interface(interface).get_mtu() area_mtu = isis['lsp_mtu'] - if mtu < int(area_mtu): - raise ConfigError(f'Interface {interface} has MTU {mtu}, minimum ' \ - f'area MTU is {area_mtu}!') + # Recommended maximum PDU size = interface MTU - 3 bytes + recom_area_mtu = mtu - 3 + if mtu < int(area_mtu) or int(area_mtu) > recom_area_mtu: + raise ConfigError(f'Interface {interface} has MTU {mtu}, ' \ + f'current area MTU is {area_mtu}! \n' \ + f'Recommended area lsp-mtu {recom_area_mtu} or less ' \ + '(calculated on MTU size).') if 'vrf' in isis: # If interface specific options are set, we must ensure that the -- cgit v1.2.3 From 655876f4c22c0f4ea839a81f4af09d6016e19197 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 13 Aug 2021 15:48:14 +0000 Subject: openvpn: T3738: Disable authentication option for server mode --- src/conf_mode/interfaces-openvpn.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 74e29ed82..6be4e918b 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2021 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 @@ -273,6 +273,9 @@ def verify(openvpn): if openvpn['protocol'] == 'tcp-active': raise ConfigError('Protocol "tcp-active" is not valid in server mode') + if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn): + raise ConfigError('Cannot specify "authentication" in server mode') + if 'remote_port' in openvpn: raise ConfigError('Cannot specify "remote-port" in server mode') -- cgit v1.2.3 From da94e0a736874d9a6420ec1aa754efcec684b390 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 13 Aug 2021 21:11:58 +0200 Subject: vrf: T3734: T3728: vni must be configured with a higher priority then bgpd When removing bgp (vrf) instances the assigned VRF vni must be deleted from FRR prior the removal of the bgp settings (T3734). This is now done by moving the CLI command "set vrf name red vni 1000" to a dedicated Python script with a priority higher then bgp. --- data/configd-include.json | 1 + data/templates/frr/vrf-vni.frr.tmpl | 7 ++++ data/templates/frr/vrf.frr.tmpl | 9 ----- interface-definitions/vrf.xml.in | 15 +++++++- src/conf_mode/vrf.py | 20 ---------- src/conf_mode/vrf_vni.py | 76 +++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 data/templates/frr/vrf-vni.frr.tmpl delete mode 100644 data/templates/frr/vrf.frr.tmpl create mode 100755 src/conf_mode/vrf_vni.py (limited to 'src/conf_mode') diff --git a/data/configd-include.json b/data/configd-include.json index 3b4e2925b..2d7ea149b 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -69,5 +69,6 @@ "vpn_pptp.py", "vpn_sstp.py", "vrf.py", +"vrf_vni.py", "vrrp.py" ] diff --git a/data/templates/frr/vrf-vni.frr.tmpl b/data/templates/frr/vrf-vni.frr.tmpl new file mode 100644 index 000000000..51d4ede1b --- /dev/null +++ b/data/templates/frr/vrf-vni.frr.tmpl @@ -0,0 +1,7 @@ +{% if vrf is defined and vrf is not none %} +vrf {{ vrf }} +{% if vni is defined and vni is not none %} + vni {{ vni }} +{% endif %} + exit-vrf +{% endif %} diff --git a/data/templates/frr/vrf.frr.tmpl b/data/templates/frr/vrf.frr.tmpl deleted file mode 100644 index 299c9719e..000000000 --- a/data/templates/frr/vrf.frr.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -{% if name is defined and name is not none %} -{% for vrf, vrf_config in name.items() %} -vrf {{ vrf }} -{% if vrf_config.vni is defined and vrf_config.vni is not none %} - vni {{ vrf_config.vni }} -{% endif %} - exit-vrf -{% endfor %} -{% endif %} diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index 9d513945c..76d6df386 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -85,7 +85,20 @@ VRF routing table must be in range from 100 to 65535 - #include + + + Virtual Network Identifier + + 822 + + 0-16777214 + VXLAN virtual network identifier + + + + + + diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index c1cfc1dcb..919083ac4 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -24,7 +24,6 @@ from vyos.config import Config from vyos.configdict import node_changed from vyos.ifconfig import Interface from vyos.template import render -from vyos.template import render_to_string from vyos.util import call from vyos.util import cmd from vyos.util import dict_search @@ -32,12 +31,9 @@ from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() -frr_daemon = 'zebra' - config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf' def list_rules(): @@ -131,7 +127,6 @@ def verify(vrf): def generate(vrf): render(config_file, 'vrf/vrf.conf.tmpl', vrf) - vrf['new_frr_config'] = render_to_string('frr/vrf.frr.tmpl', vrf) # Render nftables zones config vrf['nft_vrf_zones'] = NamedTemporaryFile().name render(vrf['nft_vrf_zones'], 'firewall/nftables-vrf-zones.tmpl', vrf) @@ -242,21 +237,6 @@ def apply(vrf): if tmp == 0: cmd('nft delete table inet vrf_zones') - # T3694: Somehow we hit a priority inversion here as we need to remove the - # VRF assigned VNI before we can remove a BGP bound VRF instance. Maybe - # move this to an individual helper script that set's up the VNI for the - # given VRF after any routing protocol. - # - # # add configuration to FRR - # frr_cfg = frr.FRRConfig() - # frr_cfg.load_configuration(frr_daemon) - # frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '') - # frr_cfg.add_before(r'(interface .*|line vty)', vrf['new_frr_config']) - # frr_cfg.commit_configuration(frr_daemon) - # - # # Save configuration to /run/frr/config/frr.conf - # frr.save_configuration() - return None if __name__ == '__main__': diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py new file mode 100755 index 000000000..87ee8f2d1 --- /dev/null +++ b/src/conf_mode/vrf_vni.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2021 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 . + +from sys import argv +from sys import exit + +from vyos.config import Config +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +frr_daemon = 'zebra' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + # This script only works with a passed VRF name + if len(argv) < 1: + raise NotImplementedError + vrf = argv[1] + + # "assemble" dict - easier here then use a full blown get_config_dict() + # on a single leafNode + vni = { 'vrf' : vrf } + tmp = conf.return_value(['vrf', 'name', vrf, 'vni']) + if tmp: vni.update({ 'vni' : tmp }) + + return vni + +def verify(vni): + return None + +def generate(vni): + vni['new_frr_config'] = render_to_string('frr/vrf-vni.frr.tmpl', vni) + return None + +def apply(vni): + # add configuration to FRR + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(frr_daemon) + frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '') + frr_cfg.add_before(r'(interface .*|line vty)', vni['new_frr_config']) + frr_cfg.commit_configuration(frr_daemon) + + # Save configuration to /run/frr/config/frr.conf + frr.save_configuration() + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From e36a4e684b95c4fd1e9ab042270fafd95e697586 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 14 Aug 2021 18:13:50 +0200 Subject: ospf: T3236: use proper daemon named template file --- data/templates/frr/ospf.frr.tmpl | 179 ------------------------------------- data/templates/frr/ospf6d.frr.tmpl | 89 ++++++++++++++++++ data/templates/frr/ospfd.frr.tmpl | 179 +++++++++++++++++++++++++++++++++++++ data/templates/frr/ospfv3.frr.tmpl | 89 ------------------ src/conf_mode/protocols_ospf.py | 2 +- src/conf_mode/protocols_ospfv3.py | 4 +- 6 files changed, 271 insertions(+), 271 deletions(-) delete mode 100644 data/templates/frr/ospf.frr.tmpl create mode 100644 data/templates/frr/ospf6d.frr.tmpl create mode 100644 data/templates/frr/ospfd.frr.tmpl delete mode 100644 data/templates/frr/ospfv3.frr.tmpl (limited to 'src/conf_mode') diff --git a/data/templates/frr/ospf.frr.tmpl b/data/templates/frr/ospf.frr.tmpl deleted file mode 100644 index 36aa699a9..000000000 --- a/data/templates/frr/ospf.frr.tmpl +++ /dev/null @@ -1,179 +0,0 @@ -! -{% if interface is defined and interface is not none %} -{% for iface, iface_config in interface.items() %} -interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} -{% if iface_config.authentication is defined and iface_config.authentication is not none %} -{% if iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} - ip ospf authentication-key {{ iface_config.authentication.plaintext_password }} -{% elif iface_config.authentication.md5 is defined %} - ip ospf authentication message-digest -{% if iface_config.authentication.md5.key_id is defined and iface_config.authentication.md5.key_id is not none %} -{% for key, key_config in iface_config.authentication.md5.key_id.items() %} - ip ospf message-digest-key {{ key }} md5 {{ key_config.md5_key }} -{% endfor %} -{% endif %} -{% endif %} -{% endif %} -{% if iface_config.cost is defined and iface_config.cost is not none %} - ip ospf cost {{ iface_config.cost }} -{% endif %} -{% if iface_config.priority is defined and iface_config.priority is not none %} - ip ospf priority {{ iface_config.priority }} -{% endif %} -{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} - ip ospf hello-interval {{ iface_config.hello_interval }} -{% endif %} -{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %} - ip ospf retransmit-interval {{ iface_config.retransmit_interval }} -{% endif %} -{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %} - ip ospf transmit-delay {{ iface_config.transmit_delay }} -{% endif %} -{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %} - ip ospf dead-interval {{ iface_config.dead_interval }} -{% elif iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %} - ip ospf dead-interval minimal hello-multiplier {{ iface_config.hello_multiplier }} -{% endif %} -{% if iface_config.bfd is defined %} - ip ospf bfd -{% endif %} -{% if iface_config.mtu_ignore is defined %} - ip ospf mtu-ignore -{% endif %} -{% if iface_config.network is defined and iface_config.network is not none %} - ip ospf network {{ iface_config.network }} -{% endif %} -{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} - bandwidth {{ iface_config.bandwidth }} -{% endif %} -! -{% endfor %} -{% endif %} -! -router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} -{% if access_list is defined and access_list is not none %} -{% for acl, acl_config in access_list.items() %} -{% for protocol in acl_config.export if acl_config.export is defined %} - distribute-list {{ acl }} out {{ protocol }} -{% endfor %} -{% endfor %} -{% endif %} -{% if area is defined and area is not none %} -{% for area_id, area_config in area.items() %} -{% if area_config.area_type is defined and area_config.area_type is not none %} -{% for type, type_config in area_config.area_type.items() if type != 'normal' %} - area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} -{% if type_config.default_cost is defined and type_config.default_cost is not none %} - area {{ area_id }} default-cost {{ type_config.default_cost }} -{% endif %} -{% endfor %} -{% endif %} -{% if area_config.authentication is defined and area_config.authentication is not none %} - area {{ area_id }} authentication {{ 'message-digest' if area_config.authentication == 'md5' }} -{% endif %} -{% for network in area_config.network if area_config.network is defined %} - network {{ network }} area {{ area_id }} -{% endfor %} -{% if area_config.range is defined and area_config.range is not none %} -{% for range, range_config in area_config.range.items() %} -{% if range_config.cost is defined and range_config.cost is not none %} - area {{ area_id }} range {{ range }} cost {{ range_config.cost }} -{% endif %} -{% if range_config.not_advertise is defined %} - area {{ area_id }} range {{ range }} not-advertise -{% endif %} -{% if range_config.substitute is defined and range_config.substitute is not none %} - area {{ area_id }} range {{ range }} substitute {{ range_config.substitute }} -{% endif %} -{% endfor %} -{% endif %} -{% if area_config.shortcut is defined and area_config.shortcut is not none %} - area {{ area_id }} shortcut {{ area_config.shortcut }} -{% endif %} -{% if area_config.virtual_link is defined and area_config.virtual_link is not none %} -{% for link, link_config in area_config.virtual_link.items() %} -{% if link_config.authentication is defined and link_config.authentication is not none %} -{% if link_config.authentication.plaintext_password is defined and link_config.authentication.plaintext_password is not none %} - area {{ area_id }} virtual-link {{ link }} authentication-key {{ link_config.authentication.plaintext_password }} -{% elif link_config.authentication.md5 is defined and link_config.authentication.md5.key_id is defined and link_config.authentication.md5.key_id is not none %} -{% for key, key_config in link_config.authentication.md5.key_id.items() %} - area {{ area_id }} virtual-link {{ link }} message-digest-key {{ key }} md5 {{ key_config.md5_key }} -{% endfor %} -{% endif %} -{% endif %} -{# The following values are default values #} - area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }} -{% endfor %} -{% endif %} -{% endfor %} -{% endif %} -{% if auto_cost is defined and auto_cost.reference_bandwidth is defined and auto_cost.reference_bandwidth is not none %} - auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} -{% endif %} -{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} - default-information originate {{ 'always' if default_information.originate.always is defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is defined }} -{% endif %} -{% if default_metric is defined and default_metric is not none %} - default-metric {{ default_metric }} -{% endif %} -{% if distance is defined and distance is not none %} -{% if distance.global is defined and distance.global is not none %} - distance {{ distance.global }} -{% endif %} -{% if distance.ospf is defined and distance.ospf is not none %} - distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is defined }} -{% endif %} -{% endif %} -{% if log_adjacency_changes is defined %} - log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} -{% endif %} -{% if max_metric is defined and max_metric.router_lsa is defined and max_metric.router_lsa is not none %} -{% if max_metric.router_lsa.administrative is defined %} - max-metric router-lsa administrative -{% endif %} -{% if max_metric.router_lsa.on_shutdown is defined and max_metric.router_lsa.on_shutdown is not none %} - max-metric router-lsa on-shutdown {{ max_metric.router_lsa.on_shutdown }} -{% endif %} -{% if max_metric.router_lsa.on_startup is defined and max_metric.router_lsa.on_startup is not none %} - max-metric router-lsa on-startup {{ max_metric.router_lsa.on_startup }} -{% endif %} -{% endif %} -{% if mpls_te is defined and mpls_te.enable is defined %} - mpls-te on - mpls-te router-address {{ mpls_te.router_address }} -{% endif %} -{% if neighbor is defined and neighbor is not none%} -{% for address, address_config in neighbor.items() %} - neighbor {{ address }} {{ 'priority ' + address_config.priority if address_config.priority is defined }} {{ 'poll-interval ' + address_config.poll_interval if address_config.poll_interval is defined }} -{% endfor %} -{% endif %} -{% if parameters is defined and parameters is not none %} -{% if parameters.abr_type is defined and parameters.abr_type is not none %} - ospf abr-type {{ parameters.abr_type }} -{% endif %} -{% if parameters.router_id is defined and parameters.router_id is not none %} - ospf router-id {{ parameters.router_id }} -{% endif %} -{% endif %} -{% for interface in passive_interface if passive_interface is defined %} - passive-interface {{ interface }} -{% endfor %} -{% for interface in passive_interface_exclude if passive_interface_exclude is defined %} -{% if interface.startswith('vlink') %} -{% set interface = interface.upper() %} -{% endif %} - no passive-interface {{ interface }} -{% endfor %} -{% if redistribute is defined and redistribute is not none %} -{% for protocol, options in redistribute.items() %} - redistribute {{ protocol }} {{ 'metric ' + options.metric if options.metric is defined }} {{ 'metric-type ' + options.metric_type if options.metric_type is defined }} {{ 'route-map ' + options.route_map if options.route_map is defined }} -{% endfor %} -{% endif %} -{% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} - refresh timer {{ refresh.timers }} -{% endif %} -{% if timers is defined and timers.throttle is defined and timers.throttle.spf is defined and timers.throttle.spf is not none %} -{# Timer values have default values #} - timers throttle spf {{ timers.throttle.spf.delay }} {{ timers.throttle.spf.initial_holdtime }} {{ timers.throttle.spf.max_holdtime }} -{% endif %} -! diff --git a/data/templates/frr/ospf6d.frr.tmpl b/data/templates/frr/ospf6d.frr.tmpl new file mode 100644 index 000000000..0026c0d2c --- /dev/null +++ b/data/templates/frr/ospf6d.frr.tmpl @@ -0,0 +1,89 @@ +! +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} +{% if iface_config.cost is defined and iface_config.cost is not none %} + ipv6 ospf6 cost {{ iface_config.cost }} +{% endif %} +{% if iface_config.priority is defined and iface_config.priority is not none %} + ipv6 ospf6 priority {{ iface_config.priority }} +{% endif %} +{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} + ipv6 ospf6 hello-interval {{ iface_config.hello_interval }} +{% endif %} +{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %} + ipv6 ospf6 retransmit-interval {{ iface_config.retransmit_interval }} +{% endif %} +{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %} + ipv6 ospf6 transmit-delay {{ iface_config.transmit_delay }} +{% endif %} +{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %} + ipv6 ospf6 dead-interval {{ iface_config.dead_interval }} +{% endif %} +{% if iface_config.bfd is defined %} + ipv6 ospf6 bfd +{% endif %} +{% if iface_config.mtu_ignore is defined %} + ipv6 ospf6 mtu-ignore +{% endif %} +{% if iface_config.ifmtu is defined and iface_config.ifmtu is not none %} + ipv6 ospf6 ifmtu {{ iface_config.ifmtu }} +{% endif %} +{% if iface_config.network is defined and iface_config.network is not none %} + ipv6 ospf6 network {{ iface_config.network }} +{% endif %} +{% if iface_config.instance_id is defined and iface_config.instance_id is not none %} + ipv6 ospf6 instance-id {{ iface_config.instance_id }} +{% endif %} +{% if iface_config.passive is defined %} + ipv6 ospf6 passive +{% endif %} +! +{% endfor %} +{% endif %} +! +router ospf6 +{% if area is defined and area is not none %} +{% for area_id, area_config in area.items() %} +{% if area_config.interface is defined and area_config.interface is not none %} +{% for interface in area_config.interface %} + interface {{ interface }} area {{ area_id }} +{% endfor %} +{% endif %} +{% if area_config.area_type is defined and area_config.area_type is not none %} +{% for type, type_config in area_config.area_type.items() %} + area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} +{% endfor %} +{% endif %} +{% if area_config.range is defined and area_config.range is not none %} +{% for prefix, prefix_config in area_config.range.items() %} + area {{ area_id }} range {{ prefix }} {{ 'advertise' if prefix_config.advertise is defined }} {{ 'not-advertise' if prefix_config.not_advertise is defined }} +{% endfor %} +{% endif %} +{% if area_config.export_list is defined and area_config.export_list is not none %} + area {{ area_id }} export-list {{ area_config.export_list }} +{% endif %} +{% if area_config.import_list is defined and area_config.import_list is not none %} + area {{ area_id }} import-list {{ area_config.import_list }} +{% endif %} +{% endfor %} +{% endif %} +{% if distance is defined and distance is not none %} +{% if distance.global is defined and distance.global is not none %} + distance {{ distance.global }} +{% endif %} +{% if distance.ospfv3 is defined and distance.ospfv3 is not none %} + distance ospf6 {{ 'intra-area ' + distance.ospfv3.intra_area if distance.ospfv3.intra_area is defined }} {{ 'inter-area ' + distance.ospfv3.inter_area if distance.ospfv3.inter_area is defined }} {{ 'external ' + distance.ospfv3.external if distance.ospfv3.external is defined }} +{% endif %} +{% endif %} +{% if parameters is defined and parameters is not none %} +{% if parameters.router_id is defined and parameters.router_id is not none %} + ospf6 router-id {{ parameters.router_id }} +{% endif %} +{% endif %} +{% if redistribute is defined and redistribute is not none %} +{% for protocol, options in redistribute.items() %} + redistribute {{ protocol }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% endfor %} +{% endif %} +! diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl new file mode 100644 index 000000000..763d0666c --- /dev/null +++ b/data/templates/frr/ospfd.frr.tmpl @@ -0,0 +1,179 @@ +! +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +{% if iface_config.authentication is defined and iface_config.authentication is not none %} +{% if iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} + ip ospf authentication-key {{ iface_config.authentication.plaintext_password }} +{% elif iface_config.authentication.md5 is defined %} + ip ospf authentication message-digest +{% if iface_config.authentication.md5.key_id is defined and iface_config.authentication.md5.key_id is not none %} +{% for key, key_config in iface_config.authentication.md5.key_id.items() %} + ip ospf message-digest-key {{ key }} md5 {{ key_config.md5_key }} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} +{% if iface_config.cost is defined and iface_config.cost is not none %} + ip ospf cost {{ iface_config.cost }} +{% endif %} +{% if iface_config.priority is defined and iface_config.priority is not none %} + ip ospf priority {{ iface_config.priority }} +{% endif %} +{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} + ip ospf hello-interval {{ iface_config.hello_interval }} +{% endif %} +{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %} + ip ospf retransmit-interval {{ iface_config.retransmit_interval }} +{% endif %} +{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %} + ip ospf transmit-delay {{ iface_config.transmit_delay }} +{% endif %} +{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %} + ip ospf dead-interval {{ iface_config.dead_interval }} +{% elif iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %} + ip ospf dead-interval minimal hello-multiplier {{ iface_config.hello_multiplier }} +{% endif %} +{% if iface_config.bfd is defined %} + ip ospf bfd +{% endif %} +{% if iface_config.mtu_ignore is defined %} + ip ospf mtu-ignore +{% endif %} +{% if iface_config.network is defined and iface_config.network is not none %} + ip ospf network {{ iface_config.network }} +{% endif %} +{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} + bandwidth {{ iface_config.bandwidth }} +{% endif %} +! +{% endfor %} +{% endif %} +! +router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +{% if access_list is defined and access_list is not none %} +{% for acl, acl_config in access_list.items() %} +{% for protocol in acl_config.export if acl_config.export is defined %} + distribute-list {{ acl }} out {{ protocol }} +{% endfor %} +{% endfor %} +{% endif %} +{% if area is defined and area is not none %} +{% for area_id, area_config in area.items() %} +{% if area_config.area_type is defined and area_config.area_type is not none %} +{% for type, type_config in area_config.area_type.items() if type != 'normal' %} + area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} +{% if type_config.default_cost is defined and type_config.default_cost is not none %} + area {{ area_id }} default-cost {{ type_config.default_cost }} +{% endif %} +{% endfor %} +{% endif %} +{% if area_config.authentication is defined and area_config.authentication is not none %} + area {{ area_id }} authentication {{ 'message-digest' if area_config.authentication == 'md5' }} +{% endif %} +{% for network in area_config.network if area_config.network is defined %} + network {{ network }} area {{ area_id }} +{% endfor %} +{% if area_config.range is defined and area_config.range is not none %} +{% for range, range_config in area_config.range.items() %} +{% if range_config.cost is defined and range_config.cost is not none %} + area {{ area_id }} range {{ range }} cost {{ range_config.cost }} +{% endif %} +{% if range_config.not_advertise is defined %} + area {{ area_id }} range {{ range }} not-advertise +{% endif %} +{% if range_config.substitute is defined and range_config.substitute is not none %} + area {{ area_id }} range {{ range }} substitute {{ range_config.substitute }} +{% endif %} +{% endfor %} +{% endif %} +{% if area_config.shortcut is defined and area_config.shortcut is not none %} + area {{ area_id }} shortcut {{ area_config.shortcut }} +{% endif %} +{% if area_config.virtual_link is defined and area_config.virtual_link is not none %} +{% for link, link_config in area_config.virtual_link.items() %} +{% if link_config.authentication is defined and link_config.authentication is not none %} +{% if link_config.authentication.plaintext_password is defined and link_config.authentication.plaintext_password is not none %} + area {{ area_id }} virtual-link {{ link }} authentication-key {{ link_config.authentication.plaintext_password }} +{% elif link_config.authentication.md5 is defined and link_config.authentication.md5.key_id is defined and link_config.authentication.md5.key_id is not none %} +{% for key, key_config in link_config.authentication.md5.key_id.items() %} + area {{ area_id }} virtual-link {{ link }} message-digest-key {{ key }} md5 {{ key_config.md5_key }} +{% endfor %} +{% endif %} +{% endif %} +{# The following values are default values #} + area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% if auto_cost is defined and auto_cost.reference_bandwidth is defined and auto_cost.reference_bandwidth is not none %} + auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} +{% endif %} +{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} + default-information originate {{ 'always' if default_information.originate.always is defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is defined }} +{% endif %} +{% if default_metric is defined and default_metric is not none %} + default-metric {{ default_metric }} +{% endif %} +{% if distance is defined and distance is not none %} +{% if distance.global is defined and distance.global is not none %} + distance {{ distance.global }} +{% endif %} +{% if distance.ospf is defined and distance.ospf is not none %} + distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is defined }} +{% endif %} +{% endif %} +{% if log_adjacency_changes is defined %} + log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} +{% endif %} +{% if max_metric is defined and max_metric.router_lsa is defined and max_metric.router_lsa is not none %} +{% if max_metric.router_lsa.administrative is defined %} + max-metric router-lsa administrative +{% endif %} +{% if max_metric.router_lsa.on_shutdown is defined and max_metric.router_lsa.on_shutdown is not none %} + max-metric router-lsa on-shutdown {{ max_metric.router_lsa.on_shutdown }} +{% endif %} +{% if max_metric.router_lsa.on_startup is defined and max_metric.router_lsa.on_startup is not none %} + max-metric router-lsa on-startup {{ max_metric.router_lsa.on_startup }} +{% endif %} +{% endif %} +{% if mpls_te is defined and mpls_te.enable is defined %} + mpls-te on + mpls-te router-address {{ mpls_te.router_address }} +{% endif %} +{% if neighbor is defined and neighbor is not none%} +{% for address, address_config in neighbor.items() %} + neighbor {{ address }} {{ 'priority ' + address_config.priority if address_config.priority is defined }} {{ 'poll-interval ' + address_config.poll_interval if address_config.poll_interval is defined }} +{% endfor %} +{% endif %} +{% if parameters is defined and parameters is not none %} +{% if parameters.abr_type is defined and parameters.abr_type is not none %} + ospf abr-type {{ parameters.abr_type }} +{% endif %} +{% if parameters.router_id is defined and parameters.router_id is not none %} + ospf router-id {{ parameters.router_id }} +{% endif %} +{% endif %} +{% for interface in passive_interface if passive_interface is defined and passive_interface == 'default' %} + passive-interface default +{% endfor %} +{% for interface in passive_interface_exclude if passive_interface_exclude is defined %} +{% if interface.startswith('vlink') %} +{% set interface = interface.upper() %} +{% endif %} + no passive-interface {{ interface }} +{% endfor %} +{% if redistribute is defined and redistribute is not none %} +{% for protocol, options in redistribute.items() %} + redistribute {{ protocol }} {{ 'metric ' + options.metric if options.metric is defined }} {{ 'metric-type ' + options.metric_type if options.metric_type is defined }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% endfor %} +{% endif %} +{% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} + refresh timer {{ refresh.timers }} +{% endif %} +{% if timers is defined and timers.throttle is defined and timers.throttle.spf is defined and timers.throttle.spf is not none %} +{# Timer values have default values #} + timers throttle spf {{ timers.throttle.spf.delay }} {{ timers.throttle.spf.initial_holdtime }} {{ timers.throttle.spf.max_holdtime }} +{% endif %} +! diff --git a/data/templates/frr/ospfv3.frr.tmpl b/data/templates/frr/ospfv3.frr.tmpl deleted file mode 100644 index 0026c0d2c..000000000 --- a/data/templates/frr/ospfv3.frr.tmpl +++ /dev/null @@ -1,89 +0,0 @@ -! -{% if interface is defined and interface is not none %} -{% for iface, iface_config in interface.items() %} -interface {{ iface }} -{% if iface_config.cost is defined and iface_config.cost is not none %} - ipv6 ospf6 cost {{ iface_config.cost }} -{% endif %} -{% if iface_config.priority is defined and iface_config.priority is not none %} - ipv6 ospf6 priority {{ iface_config.priority }} -{% endif %} -{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} - ipv6 ospf6 hello-interval {{ iface_config.hello_interval }} -{% endif %} -{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %} - ipv6 ospf6 retransmit-interval {{ iface_config.retransmit_interval }} -{% endif %} -{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %} - ipv6 ospf6 transmit-delay {{ iface_config.transmit_delay }} -{% endif %} -{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %} - ipv6 ospf6 dead-interval {{ iface_config.dead_interval }} -{% endif %} -{% if iface_config.bfd is defined %} - ipv6 ospf6 bfd -{% endif %} -{% if iface_config.mtu_ignore is defined %} - ipv6 ospf6 mtu-ignore -{% endif %} -{% if iface_config.ifmtu is defined and iface_config.ifmtu is not none %} - ipv6 ospf6 ifmtu {{ iface_config.ifmtu }} -{% endif %} -{% if iface_config.network is defined and iface_config.network is not none %} - ipv6 ospf6 network {{ iface_config.network }} -{% endif %} -{% if iface_config.instance_id is defined and iface_config.instance_id is not none %} - ipv6 ospf6 instance-id {{ iface_config.instance_id }} -{% endif %} -{% if iface_config.passive is defined %} - ipv6 ospf6 passive -{% endif %} -! -{% endfor %} -{% endif %} -! -router ospf6 -{% if area is defined and area is not none %} -{% for area_id, area_config in area.items() %} -{% if area_config.interface is defined and area_config.interface is not none %} -{% for interface in area_config.interface %} - interface {{ interface }} area {{ area_id }} -{% endfor %} -{% endif %} -{% if area_config.area_type is defined and area_config.area_type is not none %} -{% for type, type_config in area_config.area_type.items() %} - area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} -{% endfor %} -{% endif %} -{% if area_config.range is defined and area_config.range is not none %} -{% for prefix, prefix_config in area_config.range.items() %} - area {{ area_id }} range {{ prefix }} {{ 'advertise' if prefix_config.advertise is defined }} {{ 'not-advertise' if prefix_config.not_advertise is defined }} -{% endfor %} -{% endif %} -{% if area_config.export_list is defined and area_config.export_list is not none %} - area {{ area_id }} export-list {{ area_config.export_list }} -{% endif %} -{% if area_config.import_list is defined and area_config.import_list is not none %} - area {{ area_id }} import-list {{ area_config.import_list }} -{% endif %} -{% endfor %} -{% endif %} -{% if distance is defined and distance is not none %} -{% if distance.global is defined and distance.global is not none %} - distance {{ distance.global }} -{% endif %} -{% if distance.ospfv3 is defined and distance.ospfv3 is not none %} - distance ospf6 {{ 'intra-area ' + distance.ospfv3.intra_area if distance.ospfv3.intra_area is defined }} {{ 'inter-area ' + distance.ospfv3.inter_area if distance.ospfv3.inter_area is defined }} {{ 'external ' + distance.ospfv3.external if distance.ospfv3.external is defined }} -{% endif %} -{% endif %} -{% if parameters is defined and parameters is not none %} -{% if parameters.router_id is defined and parameters.router_id is not none %} - ospf6 router-id {{ parameters.router_id }} -{% endif %} -{% endif %} -{% if redistribute is defined and redistribute is not none %} -{% for protocol, options in redistribute.items() %} - redistribute {{ protocol }} {{ 'route-map ' + options.route_map if options.route_map is defined }} -{% endfor %} -{% endif %} -! diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 78c1c82bd..82126cb11 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -177,7 +177,7 @@ def generate(ospf): ospf['protocol'] = 'ospf' # required for frr/vrf.route-map.frr.tmpl ospf['frr_zebra_config'] = render_to_string('frr/vrf.route-map.frr.tmpl', ospf) - ospf['frr_ospfd_config'] = render_to_string('frr/ospf.frr.tmpl', ospf) + ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.tmpl', ospf) return None def apply(ospf): diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index fef0f509b..536ffa690 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -65,7 +65,7 @@ def verify(ospfv3): if 'ifmtu' in if_config: mtu = Interface(ifname).get_mtu() if int(if_config['ifmtu']) > int(mtu): - raise ConfigError(f'OSPFv3 ifmtu cannot go beyond physical MTU of "{mtu}"') + raise ConfigError(f'OSPFv3 ifmtu can not exceed physical MTU of "{mtu}"') return None @@ -74,7 +74,7 @@ def generate(ospfv3): ospfv3['new_frr_config'] = '' return None - ospfv3['new_frr_config'] = render_to_string('frr/ospfv3.frr.tmpl', ospfv3) + ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.tmpl', ospfv3) return None def apply(ospfv3): -- cgit v1.2.3 From 43fcc0db006a12024df1b49d6f3c9e99ce165226 Mon Sep 17 00:00:00 2001 From: Lulu Cathrinus Grimalkin Date: Sun, 15 Aug 2021 13:10:20 +0300 Subject: conntrack: T3275: migrate 'disable' syntax to 'enable' syntax for the new default behavior --- .../include/conntrack-module-disable.xml.i | 8 ---- interface-definitions/system-conntrack.xml.in | 44 ++++++++-------------- src/conf_mode/conntrack.py | 2 +- src/migration-scripts/conntrack/2-to-3 | 37 ++++++++++++++++++ 4 files changed, 53 insertions(+), 38 deletions(-) delete mode 100644 interface-definitions/include/conntrack-module-disable.xml.i create mode 100755 src/migration-scripts/conntrack/2-to-3 (limited to 'src/conf_mode') diff --git a/interface-definitions/include/conntrack-module-disable.xml.i b/interface-definitions/include/conntrack-module-disable.xml.i deleted file mode 100644 index f891225e0..000000000 --- a/interface-definitions/include/conntrack-module-disable.xml.i +++ /dev/null @@ -1,8 +0,0 @@ - - - - Disable connection tracking helper - - - - diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index fa73df3db..c408e9bdd 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -37,64 +37,50 @@ - Connection tracking modules settings + Connection tracking modules - FTP connection tracking settings + FTP connection tracking + - - #include - - H.323 connection tracking settings + H.323 connection tracking + - - #include - - NFS connection tracking settings + NFS connection tracking + - - #include - - PPTP connection tracking settings + PPTP connection tracking + - - #include - - SIP connection tracking settings + SIP connection tracking + - - #include - - SQLnet connection tracking settings + SQLnet connection tracking + - - #include - - TFTP connection tracking settings + TFTP connection tracking + - - #include - diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 4e6e39c0f..b305265db 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -97,7 +97,7 @@ def apply(conntrack): # Depending on the enable/disable state of the ALG (Application Layer Gateway) # modules we need to either insmod or rmmod the helpers. for module, module_config in module_map.items(): - if dict_search(f'modules.{module}.disable', conntrack) != None: + if dict_search(f'modules.{module}', conntrack) is None: if 'ko' in module_config: for mod in module_config['ko']: # Only remove the module if it's loaded diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3 new file mode 100755 index 000000000..8a8b43279 --- /dev/null +++ b/src/migration-scripts/conntrack/2-to-3 @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +# Conntrack syntax version 3 +# Enables all conntrack modules (previous default behaviour) and omits manually disabled modules. + +import sys + +from vyos.configtree import ConfigTree +from vyos.version import get_version + +if len(sys.argv) < 1: + print('Must specify file name!') + sys.exit(1) + +filename = sys.argv[1] + +with open(filename, 'r') as f: + config = ConfigTree(f.read()) + +module_path = ['system', 'conntrack', 'modules'] + +# Go over all conntrack modules available as of v1.3.0. +for module in ['ftp', 'h323', 'nfs', 'pptp', 'sip', 'sqlnet', 'tftp']: + # 'disable' is being phased out. + if config.exists(module_path + [module, 'disable']): + config.delete(module_path + [module]) + # If it wasn't manually 'disable'd, it was enabled by default. + else: + config.set(module_path + [module]) + +try: + if config.exists(module_path): + with open(filename, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + sys.exit(1) -- cgit v1.2.3 From 6f87d8c910964fd0ebe9724183baa12861caa419 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 15 Aug 2021 12:53:03 +0200 Subject: ospf: T3757: support to configure area at an interface level FRR supports configuring either network prefixes per area, or assign an interface to an area to participate in the routing process. This is already well known from other venders and supported by FRR. A valid VyOS OSPF configuration would then look like: vyos@vyos# show protocols ospf { interface dum0 { area 0 } interface eth0.201 { area 0 authentication { md5 { key-id 10 { md5-key vyos } } } dead-interval 40 hello-interval 10 priority 1 retransmit-interval 5 transmit-delay 1 } log-adjacency-changes { detail } parameters { abr-type cisco router-id 172.18.254.201 } passive-interface default passive-interface-exclude eth0.201 } --- data/templates/frr/ospfd.frr.tmpl | 9 ++++++--- .../include/ospf/protocol-common-config.xml.i | 17 +++++++++++++++++ src/conf_mode/protocols_ospf.py | 9 +++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index 763d0666c..67b679d66 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -14,6 +14,12 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% endif %} {% endif %} +{% if iface_config.area is defined and iface_config.area is not none %} + ip ospf area {{ iface_config.area }} +{% endif %} +{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} + bandwidth {{ iface_config.bandwidth }} +{% endif %} {% if iface_config.cost is defined and iface_config.cost is not none %} ip ospf cost {{ iface_config.cost }} {% endif %} @@ -43,9 +49,6 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% if iface_config.network is defined and iface_config.network is not none %} ip ospf network {{ iface_config.network }} {% endif %} -{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %} - bandwidth {{ iface_config.bandwidth }} -{% endif %} ! {% endfor %} {% endif %} diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index db39b1a86..c4ca613a4 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -361,6 +361,23 @@ + + + Enable OSPF on this interface + + u32 + OSPF area ID as decimal notation + + + ipv4 + OSPF area ID in IP address notation + + + + + + + #include #include #include diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 82126cb11..a21ea6c9f 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -157,6 +157,15 @@ def verify(ospf): raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \ f'concurrently for {interface}!') + # One can not use the "network area " command and an + # per interface area assignment at the same time. FRR will error + # out using: "Please remove all network commands first." + if 'area' in ospf: + for area, area_config in ospf['area'].items(): + if 'network' in area_config: + raise ConfigError('Can not use OSPF interface area and area ' \ + 'network configuration at the same time!') + if 'vrf' in ospf: # If interface specific options are set, we must ensure that the # interface is bound to our requesting VRF. Due to the VyOS -- cgit v1.2.3 From 1a62587a57d034681bd728b10b13a5ae5d7d2d4d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Sun, 15 Aug 2021 14:11:41 +0000 Subject: pbr: T3702: Fix incorrect splits for fwmark --- src/conf_mode/policy-local-route.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 0b7ceedeb..539189442 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -114,15 +114,15 @@ def apply(pbr): # Only fwmark in the rule # set policy local-route rule 101 fwmark '23' if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]: - for fwmk in pbr['rule'][rule]['fwmark']: - call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}') + fwmk = pbr['rule'][rule]['fwmark'] + call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}') # Source and fwmark in the rule # set policy local-route rule 100 source '203.0.113.1' # set policy local-route rule 100 fwmark '23' if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]: + fwmk = pbr['rule'][rule]['fwmark'] for src in pbr['rule'][rule]['source']: - for fwmk in pbr['rule'][rule]['fwmark']: - call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}') + call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}') return None -- cgit v1.2.3 From 9bae7536bb6b512e6e35664bb3ebef2236011a7c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 16 Aug 2021 18:04:15 +0200 Subject: ospf: T3757: verify() bugfix for interface area Commit 6f87d8c9 ("ospf: T3757: support to configure area at an interface level") did not allow the old way an area and netwokr was set-up as the if expression was missing a check if 'area' was set in both the interface and the ospf process. --- src/conf_mode/protocols_ospf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index a21ea6c9f..06a29106d 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -149,18 +149,18 @@ def verify(ospf): if route_map_name: verify_route_map(route_map_name, ospf) if 'interface' in ospf: - for interface in ospf['interface']: + for interface, interface_config in ospf['interface'].items(): verify_interface_exists(interface) # One can not use dead-interval and hello-multiplier at the same # time. FRR will only activate the last option set via CLI. - if {'hello_multiplier', 'dead_interval'} <= set(ospf['interface'][interface]): + if {'hello_multiplier', 'dead_interval'} <= set(interface_config): raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \ f'concurrently for {interface}!') # One can not use the "network area " command and an # per interface area assignment at the same time. FRR will error # out using: "Please remove all network commands first." - if 'area' in ospf: + if 'area' in ospf and 'area' in interface_config: for area, area_config in ospf['area'].items(): if 'network' in area_config: raise ConfigError('Can not use OSPF interface area and area ' \ -- cgit v1.2.3 From 2c17993105b635c3c157e9f528a017bc9e0b556b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 16 Aug 2021 18:10:01 +0200 Subject: conntrack: T3579: remove debug print() --- src/conf_mode/conntrack.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index b305265db..9693de493 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -105,7 +105,6 @@ def apply(conntrack): cmd(f'rmmod {mod}') if 'iptables' in module_config: for rule in module_config['iptables']: - print(f'iptables --delete {rule}') cmd(f'iptables --delete {rule}') else: if 'ko' in module_config: -- cgit v1.2.3 From 18ac0c694a3081931fecc9e5e8ea48b019105d81 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 16 Aug 2021 18:25:28 +0200 Subject: conntrack: T3579: bugfix when deleting non existent iptable rules We only delete iptables rules if they really exist - if we try to delete a non- existing rule a PermissionError exception is thrown. We could either ignore the error code (that is what the old Vyatta code did), or we check what we are doing beforehand. --- src/conf_mode/conntrack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 9693de493..68877f794 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -105,7 +105,9 @@ def apply(conntrack): cmd(f'rmmod {mod}') if 'iptables' in module_config: for rule in module_config['iptables']: - cmd(f'iptables --delete {rule}') + # Only install iptables rule if it does not exist + tmp = run(f'iptables --check {rule}') + if tmp == 0: cmd(f'iptables --delete {rule}') else: if 'ko' in module_config: for mod in module_config['ko']: @@ -114,9 +116,7 @@ def apply(conntrack): for rule in module_config['iptables']: # Only install iptables rule if it does not exist tmp = run(f'iptables --check {rule}') - if tmp > 0: - cmd(f'iptables --insert {rule}') - + if tmp > 0: cmd(f'iptables --insert {rule}') if process_named_running('conntrackd'): # Reload conntrack-sync daemon to fetch new sysctl values -- cgit v1.2.3 From da453ff4a810463febacdba7246d19ac6075b9b7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 17 Aug 2021 22:04:23 +0200 Subject: bgp: T2771: adjust verify() logic to common coding style for validation --- src/conf_mode/protocols_bgp.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 9ecfd07fe..fb128db7b 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -221,27 +221,22 @@ def verify(bgp): raise ConfigError(f'Peer-group "{peer_group}" requires remote-as to be set!') # Throw an error if the global administrative distance parameters aren't all filled out. - if dict_search('parameters.distance', bgp) == None: - pass - else: - if dict_search('parameters.distance.global', bgp): - for key in ['external', 'internal', 'local']: - if dict_search(f'parameters.distance.global.{key}', bgp) == None: - raise ConfigError('Missing mandatory configuration option for '\ - f'global administrative distance {key}!') - - # Throw an error if the address family specific administrative distance parameters aren't all filled out. - if dict_search('address_family', bgp) == None: - pass - else: - for address_family_name in dict_search('address_family', bgp): - if dict_search(f'address_family.{address_family_name}.distance', bgp) == None: - pass - else: + if dict_search('parameters.distance.global', bgp) != None: + for key in ['external', 'internal', 'local']: + if dict_search(f'parameters.distance.global.{key}', bgp) == None: + raise ConfigError('Missing mandatory configuration option for '\ + f'global administrative distance {key}!') + + # Address Family specific validation + if 'address_family' in bgp: + for afi, afi_config in bgp['address_family'].items(): + if 'distance' in afi_config: + # Throw an error if the address family specific administrative + # distance parameters aren't all filled out. for key in ['external', 'internal', 'local']: - if dict_search(f'address_family.{address_family_name}.distance.{key}', bgp) == None: + if key not in afi_config['distance']: raise ConfigError('Missing mandatory configuration option for '\ - f'{address_family_name} administrative distance {key}!') + f'{afi} administrative distance {key}!') return None -- cgit v1.2.3 From 053f586fa7a5a69b3b6be81339c73d8550d67fc6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 17 Aug 2021 22:05:21 +0200 Subject: bgp: T3759: add l3vpn "import vrf" commands --- data/templates/frr/bgpd.frr.tmpl | 9 +++++++- .../include/bgp/afi-export-import.xml.i | 13 +++++++++++ smoketest/scripts/cli/test_protocols_bgp.py | 25 ++++++++++++++++++---- src/conf_mode/protocols_bgp.py | 11 ++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index a21a2fefe..2f2e94ce0 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -311,8 +311,15 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if afi_config.export is defined and afi_config.export.vpn is defined %} export vpn {% endif %} -{% if afi_config.import is defined and afi_config.import.vpn is defined %} +{% if afi_config.import is defined and afi_config.import is not none %} +{% if afi_config.import.vpn is defined %} import vpn +{% endif %} +{% if afi_config.import.vrf is defined and afi_config.import.vrf is not none %} +{% for vrf in afi_config.import.vrf %} + import vrf {{ vrf }} +{% endfor %} +{% endif %} {% endif %} {% if afi_config.local_install is defined and afi_config.local_install is not none %} {% for interface in afi_config.local_install.interface %} diff --git a/interface-definitions/include/bgp/afi-export-import.xml.i b/interface-definitions/include/bgp/afi-export-import.xml.i index ad54c723e..86817cdb3 100644 --- a/interface-definitions/include/bgp/afi-export-import.xml.i +++ b/interface-definitions/include/bgp/afi-export-import.xml.i @@ -23,6 +23,19 @@ + + + VRF to import from + + txt + VRF instance name + + + vrf name + + + + diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 22e892e26..7a470abf9 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -628,6 +628,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # templates and Jinja2 FRR template. table = '1000' + self.cli_set(base_path + ['local-as', ASN]) + # testing only one AFI is sufficient as it's generic code + for vrf in vrfs: vrf_base = ['vrf', 'name', vrf] self.cli_set(vrf_base + ['table', table]) @@ -636,15 +639,26 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(vrf_base + ['protocols', 'bgp', 'route-map', route_map_in]) table = str(int(table) + 1000) + # import VRF routes do main RIB + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'import', 'vrf', vrf]) + self.cli_commit() + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv6 unicast', frrconfig) + + for vrf in vrfs: + self.assertIn(f' import vrf {vrf}', frrconfig) + # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') - self.assertIn(f'router bgp {ASN} vrf {vrf}', frrconfig) - self.assertIn(f' bgp router-id {router_id}', frrconfig) + frr_vrf_config = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') + self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config) + self.assertIn(f' bgp router-id {router_id}', frr_vrf_config) - # CCC: Currently this is not working as FRR() class does not support + # XXX: Currently this is not working as FRR() class does not support # route-maps for multiple vrfs because the modify_section() only works # on lines and not text blocks. # @@ -694,6 +708,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {interface} activate', frrconfig) self.assertIn(f' exit-address-family', frrconfig) + def test_bgp_13_solo(self): remote_asn = str(int(ASN) + 150) neighbor = '192.0.2.55' @@ -710,9 +725,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {neighbor} solo', frrconfig) + def test_bgp_14_vpn(self): remote_asn = str(int(ASN) + 150) neighbor = '192.0.2.55' + vrf_name = 'red' self.cli_set(base_path + ['local-as', ASN]) # testing only one AFI is sufficient as it's generic code diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index fb128db7b..7fc4a2247 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_prefix_list from vyos.configverify import verify_route_map +from vyos.configverify import verify_vrf from vyos.template import is_ip from vyos.template import is_interface from vyos.template import render_to_string @@ -238,6 +239,16 @@ def verify(bgp): raise ConfigError('Missing mandatory configuration option for '\ f'{afi} administrative distance {key}!') + if afi in ['ipv4_unicast', 'ipv6_unicast']: + if 'import' in afi_config and 'vrf' in afi_config['import']: + # Check if VRF exists + verify_vrf(afi_config['import']['vrf']) + + # FRR error: please unconfigure vpn to vrf commands before + # using import vrf commands + if 'vpn' in afi_config['import'] or dict_search('export.vpn', afi_config) != None: + raise ConfigError('Please unconfigure VPN to VRF commands before '\ + 'using "import vrf" commands!') return None def generate(bgp): -- cgit v1.2.3 From c9ad82ef583a1f44f84c7e69124f2a5d868da857 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Aug 2021 09:30:33 +0200 Subject: nat66: ndppd: T2518: rename Jinja2 template folder to match common naming convention --- data/templates/ndppd/ndppd.conf.tmpl | 44 ++++++++++++++++++++++++++++++++ data/templates/proxy-ndp/ndppd.conf.tmpl | 44 -------------------------------- src/conf_mode/nat66.py | 10 ++++---- 3 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 data/templates/ndppd/ndppd.conf.tmpl delete mode 100644 data/templates/proxy-ndp/ndppd.conf.tmpl (limited to 'src/conf_mode') diff --git a/data/templates/ndppd/ndppd.conf.tmpl b/data/templates/ndppd/ndppd.conf.tmpl new file mode 100644 index 000000000..502dab5b8 --- /dev/null +++ b/data/templates/ndppd/ndppd.conf.tmpl @@ -0,0 +1,44 @@ +######################################################## +# +# autogenerated by nat66.py +# +# The configuration file must define one upstream +# interface. +# +# For some services, such as nat66, because it runs +# stateless, it needs to rely on NDP Proxy to respond +# to NDP requests. +# +# When using nat66 source rules, NDP Proxy needs +# to be enabled +# +######################################################## + +{% set global = namespace(ndppd_interfaces = [],ndppd_prefixs = []) %} +{% if source is defined and source.rule is defined and source.rule is not none %} +{% for rule, config in source.rule.items() if config.disable is not defined %} +{% if config.outbound_interface is defined %} +{% if config.outbound_interface not in global.ndppd_interfaces %} +{% set global.ndppd_interfaces = global.ndppd_interfaces + [config.outbound_interface] %} +{% endif %} +{% if config.translation is defined and config.translation.address is defined and config.translation.address | is_ip_network %} +{% set global.ndppd_prefixs = global.ndppd_prefixs + [{'interface':config.outbound_interface,'rule':config.translation.address}] %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} + +{% for interface in global.ndppd_interfaces %} +proxy {{ interface }} { + router yes + timeout 500 + ttl 30000 +{% for map in global.ndppd_prefixs %} +{% if map.interface == interface %} + rule {{ map.rule }} { + static + } +{% endif %} +{% endfor %} +} +{% endfor %} diff --git a/data/templates/proxy-ndp/ndppd.conf.tmpl b/data/templates/proxy-ndp/ndppd.conf.tmpl deleted file mode 100644 index 502dab5b8..000000000 --- a/data/templates/proxy-ndp/ndppd.conf.tmpl +++ /dev/null @@ -1,44 +0,0 @@ -######################################################## -# -# autogenerated by nat66.py -# -# The configuration file must define one upstream -# interface. -# -# For some services, such as nat66, because it runs -# stateless, it needs to rely on NDP Proxy to respond -# to NDP requests. -# -# When using nat66 source rules, NDP Proxy needs -# to be enabled -# -######################################################## - -{% set global = namespace(ndppd_interfaces = [],ndppd_prefixs = []) %} -{% if source is defined and source.rule is defined and source.rule is not none %} -{% for rule, config in source.rule.items() if config.disable is not defined %} -{% if config.outbound_interface is defined %} -{% if config.outbound_interface not in global.ndppd_interfaces %} -{% set global.ndppd_interfaces = global.ndppd_interfaces + [config.outbound_interface] %} -{% endif %} -{% if config.translation is defined and config.translation.address is defined and config.translation.address | is_ip_network %} -{% set global.ndppd_prefixs = global.ndppd_prefixs + [{'interface':config.outbound_interface,'rule':config.translation.address}] %} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} - -{% for interface in global.ndppd_interfaces %} -proxy {{ interface }} { - router yes - timeout 500 - ttl 30000 -{% for map in global.ndppd_prefixs %} -{% if map.interface == interface %} - rule {{ map.rule }} { - static - } -{% endif %} -{% endfor %} -} -{% endfor %} diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index e2bd6417d..2314b6623 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 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 @@ -55,7 +55,7 @@ def get_config(config=None): conf = config else: conf = Config() - + base = ['nat66'] nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) @@ -90,7 +90,7 @@ def get_config(config=None): # be done only once if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): nat['helper_functions'] = 'add' - + # Retrieve current table handler positions nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE') nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') @@ -109,7 +109,7 @@ def verify(nat): if 'helper_functions' in nat and nat['helper_functions'] != 'has': if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']): raise Exception('could not determine nftable ruleset handlers') - + if dict_search('source.rule', nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT66 configuration error in rule {rule}:' @@ -145,7 +145,7 @@ def verify(nat): def generate(nat): render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755) - render(ndppd_config, 'proxy-ndp/ndppd.conf.tmpl', nat, permission=0o755) + render(ndppd_config, 'ndppd/ndppd.conf.tmpl', nat, permission=0o755) return None def apply(nat): -- cgit v1.2.3 From 6b2c3906c3ef1e8a72d6923fcea9cc340e59dd82 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Aug 2021 09:51:29 +0200 Subject: nptv6: T2518: add missing verify() stage for mandatory translation address --- src/conf_mode/nat66.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 2314b6623..8a3adc807 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -124,6 +124,8 @@ def verify(nat): if addr != None: if addr != 'masquerade' and not is_ipv6(addr): raise ConfigError(f'Warning: IPv6 address {addr} is not a valid address') + else: + raise ConfigError(f'{err_msg} translation address not specified') prefix = dict_search('source.prefix', config) if prefix != None: -- cgit v1.2.3 From 39ba9c31853838739659f4842d02a7e931107710 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Aug 2021 09:52:40 +0200 Subject: nptv6: T2518: remove superfluous else clause on missing outbound-interface --- src/conf_mode/nat66.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 8a3adc807..f8bc073bb 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -114,11 +114,10 @@ def verify(nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT66 configuration error in rule {rule}:' if 'outbound_interface' not in config: - raise ConfigError(f'{err_msg}\n' \ - 'outbound-interface not specified') - else: - if config['outbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + raise ConfigError(f'{err_msg} outbound-interface not specified') + + if config['outbound_interface'] not in interfaces(): + print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') addr = dict_search('translation.address', config) if addr != None: -- cgit v1.2.3 From 2606f95cb07f55716f4b205a0e88be259936703e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Aug 2021 09:53:27 +0200 Subject: nat: T2198: remove superfluous else clause on missing outbound-interface --- src/conf_mode/nat.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index dae958774..59939d0fb 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -139,12 +139,10 @@ def verify(nat): for rule, config in dict_search('source.rule', nat).items(): err_msg = f'Source NAT configuration error in rule {rule}:' if 'outbound_interface' not in config: - raise ConfigError(f'{err_msg}\n' \ - 'outbound-interface not specified') - else: - if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + raise ConfigError(f'{err_msg} outbound-interface not specified') + if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): + print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') addr = dict_search('translation.address', config) if addr != None: -- cgit v1.2.3 From 47261d051759a743a36089aa0be7cecd348710c1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Aug 2021 14:06:00 +0200 Subject: bgp: evpn: T1513: VNI rt and rd are only supported under EVPN VRF --- src/conf_mode/protocols_bgp.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 7fc4a2247..bc7acaf6c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -249,6 +249,16 @@ def verify(bgp): if 'vpn' in afi_config['import'] or dict_search('export.vpn', afi_config) != None: raise ConfigError('Please unconfigure VPN to VRF commands before '\ 'using "import vrf" commands!') + + if afi in ['l2vpn_evpn'] and 'vrf' not in bgp: + # Some L2VPN EVPN AFI options are only supported under VRF + if 'vni' in afi_config: + for vni, vni_config in afi_config['vni'].items(): + if 'rd' in vni_config: + raise ConfigError('VNI route-distinguisher is only supported under EVPN VRF') + if 'route_target' in vni_config: + raise ConfigError('VNI route-target is only supported under EVPN VRF') + return None def generate(bgp): -- cgit v1.2.3 From f7e3f5562458da4d80b272f1524c27727799e57b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 20 Aug 2021 18:17:54 +0200 Subject: bgp: T3759: add IPv4/IPv6 unicast AFI route-map for VPN import/export This adds the following new commands: set protocols bgp address-family ipv4-unicast route-map vpn export foo-map-out set protocols bgp address-family ipv4-unicast route-map vpn import foo-map-in set protocols bgp address-family ipv6-unicast route-map vpn export foo-map-out set protocols bgp address-family ipv6-unicast route-map vpn import foo-map-in --- data/templates/frr/bgpd.frr.tmpl | 8 ++++++++ .../include/bgp/afi-route-map-vpn.xml.i | 17 +++++++++++++++++ .../include/bgp/protocol-common-config.xml.i | 2 ++ smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ src/conf_mode/protocols_bgp.py | 5 +++++ 5 files changed, 34 insertions(+) create mode 100644 interface-definitions/include/bgp/afi-route-map-vpn.xml.i (limited to 'src/conf_mode') diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 143960e6b..96815836b 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -380,6 +380,14 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% endif %} {% endif %} {% endif %} +{% if afi_config.route_map is defined and afi_config.route_map.vpn is defined and afi_config.route_map.vpn is not none %} +{% if afi_config.route_map.vpn.export is defined and afi_config.route_map.vpn.export is not none %} + route-map vpn export {{ afi_config.route_map.vpn.export }} +{% endif %} +{% if afi_config.route_map.vpn.import is defined and afi_config.route_map.vpn.import is not none %} + route-map vpn import {{ afi_config.route_map.vpn.import }} +{% endif %} +{% endif %} {% if afi_config.vni is defined and afi_config.vni is not none %} {% for vni, vni_config in afi_config.vni.items() %} vni {{ vni }} diff --git a/interface-definitions/include/bgp/afi-route-map-vpn.xml.i b/interface-definitions/include/bgp/afi-route-map-vpn.xml.i new file mode 100644 index 000000000..e6be113c5 --- /dev/null +++ b/interface-definitions/include/bgp/afi-route-map-vpn.xml.i @@ -0,0 +1,17 @@ + + + + Route-map to filter route updates to/from this peer + + + + + Between current address-family and VPN + + + #include + + + + + diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 6fb9adf93..a971c52b8 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -118,6 +118,7 @@ #include + #include Specify route distinguisher @@ -518,6 +519,7 @@ #include + #include Redistribute routes from other protocols into BGP diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index dbe1a81d6..f535408db 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -751,6 +751,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' import vpn', afi_config) self.assertIn(f' label vpn export {label}', afi_config) self.assertIn(f' rd vpn export {rd}', afi_config) + self.assertIn(f' route-map vpn export {route_map_out}', afi_config) + self.assertIn(f' route-map vpn import {route_map_in}', afi_config) self.assertIn(f' exit-address-family', afi_config) afi_config = self.getFRRconfig(f' address-family ipv4 unicast', endsection='exit-address-family', daemon='bgpd') diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index bc7acaf6c..7d05eed9f 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -250,6 +250,11 @@ def verify(bgp): raise ConfigError('Please unconfigure VPN to VRF commands before '\ 'using "import vrf" commands!') + # Verify that the export/import route-maps do exist + for export_import in ['export', 'import']: + tmp = dict_search(f'route_map.vpn.{export_import}', afi_config) + if tmp: verify_route_map(tmp, bgp) + if afi in ['l2vpn_evpn'] and 'vrf' not in bgp: # Some L2VPN EVPN AFI options are only supported under VRF if 'vni' in afi_config: -- cgit v1.2.3 From b7bfcb6ef0e712bb8c39241051e716a833b2ffe8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 19 Aug 2021 18:14:13 +0200 Subject: interfaces: T3090: migrate adjust-mss from "firewall options" to "interface" level Getting rid of "set firewall options" and move it from: set firewall options interface ethX adjust-mss 1400 set firewall options interface ethX adjust-mss6 1400 to: set interfaces ethernet ethX ip adjust-mss 1400 set interfaces ethernet ethX ipv6 adjust-mss 1400 In addition add an extra option called clamp-mss-to-pmtu instead of a value. --- data/configd-include.json | 1 - interface-definitions/firewall-options.xml.in | 50 ------- .../include/interface/adjust-mss.xml.i | 23 ++++ .../include/interface/ipv4-options.xml.i | 1 + .../include/interface/ipv6-options.xml.i | 1 + interface-definitions/interfaces-pppoe.xml.in | 2 + python/vyos/ifconfig/interface.py | 66 +++++++++ python/vyos/ifconfig/pppoe.py | 4 +- smoketest/scripts/cli/base_interfaces_test.py | 22 ++- src/conf_mode/firewall_options.py | 150 --------------------- src/migration-scripts/firewall/5-to-6 | 63 +++++++++ 11 files changed, 177 insertions(+), 206 deletions(-) delete mode 100644 interface-definitions/firewall-options.xml.in create mode 100644 interface-definitions/include/interface/adjust-mss.xml.i delete mode 100755 src/conf_mode/firewall_options.py create mode 100755 src/migration-scripts/firewall/5-to-6 (limited to 'src/conf_mode') diff --git a/data/configd-include.json b/data/configd-include.json index 2d7ea149b..6893aaa86 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -6,7 +6,6 @@ "dhcpv6_relay.py", "dns_forwarding.py", "dynamic_dns.py", -"firewall_options.py", "host_name.py", "https.py", "igmp_proxy.py", diff --git a/interface-definitions/firewall-options.xml.in b/interface-definitions/firewall-options.xml.in deleted file mode 100644 index 8d9225a9a..000000000 --- a/interface-definitions/firewall-options.xml.in +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - Firewall options/Packet manipulation - 990 - - - - - Interface clamping options - - - - - - #include - - - Adjust MSS for IPv4 transit packets - - 500-1460 - TCP Maximum segment size in bytes - - - - - - - - - Adjust MSS for IPv6 transit packets - - 1280-1492 - TCP Maximum segment size in bytes - - - - - - - - - - - - - diff --git a/interface-definitions/include/interface/adjust-mss.xml.i b/interface-definitions/include/interface/adjust-mss.xml.i new file mode 100644 index 000000000..57019f02c --- /dev/null +++ b/interface-definitions/include/interface/adjust-mss.xml.i @@ -0,0 +1,23 @@ + + + + + Adjust TCP MSS value + + clamp-mss-to-pmtu + + + clamp-mss-to-pmtu + Automatically sets the MSS to the proper value + + + u32:500-65535 + TCP Maximum segment size in bytes + + + + ^(clamp-mss-to-pmtu)$ + + + + diff --git a/interface-definitions/include/interface/ipv4-options.xml.i b/interface-definitions/include/interface/ipv4-options.xml.i index 10884b6eb..bca1229c6 100644 --- a/interface-definitions/include/interface/ipv4-options.xml.i +++ b/interface-definitions/include/interface/ipv4-options.xml.i @@ -4,6 +4,7 @@ IPv4 routing parameters + #include #include #include #include diff --git a/interface-definitions/include/interface/ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i index e57c242b0..2d2d1d3b2 100644 --- a/interface-definitions/include/interface/ipv6-options.xml.i +++ b/interface-definitions/include/interface/ipv6-options.xml.i @@ -4,6 +4,7 @@ IPv6 routing parameters + #include #include #include #include diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 1bbfa63af..ac8fa378b 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -70,6 +70,7 @@ IPv4 routing parameters + #include #include @@ -86,6 +87,7 @@ #include + #include diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index a1928ba51..53b57a83f 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -436,6 +436,62 @@ class Interface(Control): """ return self.set_interface('arp_cache_tmo', tmo) + def set_tcp_ipv4_mss(self, mss): + """ + Set IPv4 TCP MSS value advertised when TCP SYN packets leave this + interface. Value is in bytes. + + A value of 0 will disable the MSS adjustment + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_tcp_ipv4_mss(1340) + """ + iptables_bin = 'iptables' + base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = self._cmd(f'{iptables_bin}-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + # remove OLD MSS mangling configuration + line = line.replace('-A FORWARD', '-D FORWARD') + self._cmd(f'{iptables_bin} -t mangle {line}') + + cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS' + if mss == 'clamp-mss-to-pmtu': + self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu') + elif int(mss) > 0: + # probably add option to clamp only if bigger: + low_mss = str(int(mss) + 1) + self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}') + + def set_tcp_ipv6_mss(self, mss): + """ + Set IPv6 TCP MSS value advertised when TCP SYN packets leave this + interface. Value is in bytes. + + A value of 0 will disable the MSS adjustment + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_tcp_mss(1320) + """ + iptables_bin = 'ip6tables' + base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = self._cmd(f'{iptables_bin}-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + # remove OLD MSS mangling configuration + line = line.replace('-A FORWARD', '-D FORWARD') + self._cmd(f'{iptables_bin} -t mangle {line}') + + cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS' + if mss == 'clamp-mss-to-pmtu': + self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu') + elif int(mss) > 0: + # probably add option to clamp only if bigger: + low_mss = str(int(mss) + 1) + self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}') + def set_arp_filter(self, arp_filter): """ Filter ARP requests @@ -1202,6 +1258,16 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) + # Configure MSS value for IPv4 TCP connections + tmp = dict_search('ip.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv4_mss(value) + + # Configure MSS value for IPv6 TCP connections + tmp = dict_search('ipv6.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv6_mss(value) + # Configure ARP cache timeout in milliseconds - has default value tmp = dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 65575cf99..6acf7d1c7 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -17,9 +17,7 @@ from vyos.ifconfig.interface import Interface @Interface.register class PPPoEIf(Interface): - default = { - 'type': 'pppoe', - } + iftype = 'pppoe' definition = { **Interface.definition, **{ diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 7f69b8444..63f742a8d 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -556,13 +556,16 @@ class BasicInterfaceTest: if not self._test_ip: self.skipTest('not supported') + arp_tmo = '300' + mss = '1420' + for interface in self._interfaces: - arp_tmo = '300' path = self._base_path + [interface] for option in self._options.get(interface, []): self.cli_set(path + option.split()) # Options + self.cli_set(path + ['ip', 'adjust-mss', mss]) self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo]) self.cli_set(path + ['ip', 'disable-arp-filter']) self.cli_set(path + ['ip', 'disable-forwarding']) @@ -576,6 +579,12 @@ class BasicInterfaceTest: self.cli_commit() for interface in self._interfaces: + base_options = f'-A FORWARD -o {interface} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = cmd('sudo iptables-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'--set-mss {mss}', line) + tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms') self.assertEqual(tmp, str((int(arp_tmo) * 1000))) # tmo value is in milli seconds @@ -607,19 +616,28 @@ class BasicInterfaceTest: if not self._test_ipv6: self.skipTest('not supported') + mss = '1400' + dad_transmits = '10' + for interface in self._interfaces: - dad_transmits = '10' path = self._base_path + [interface] for option in self._options.get(interface, []): self.cli_set(path + option.split()) # Options + self.cli_set(path + ['ipv6', 'adjust-mss', mss]) self.cli_set(path + ['ipv6', 'disable-forwarding']) self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) self.cli_commit() for interface in self._interfaces: + base_options = f'-A FORWARD -o {interface} -p tcp -m tcp --tcp-flags SYN,RST SYN' + out = cmd('sudo ip6tables-save -t mangle') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'--set-mss {mss}', line) + tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/forwarding') self.assertEqual('0', tmp) diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py deleted file mode 100755 index 67bf5d0e2..000000000 --- a/src/conf_mode/firewall_options.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 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 . -# - -import sys -import os -import copy - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import call - -from vyos import airbag -airbag.enable() - -default_config_data = { - 'intf_opts': [], - 'new_chain4': False, - 'new_chain6': False -} - -def get_config(config=None): - opts = copy.deepcopy(default_config_data) - if config: - conf = config - else: - conf = Config() - if not conf.exists('firewall options'): - # bail out early - return opts - else: - conf.set_level('firewall options') - - # Parse configuration of each individual instance - if conf.exists('interface'): - for intf in conf.list_nodes('interface'): - conf.set_level('firewall options interface {0}'.format(intf)) - config = { - 'intf': intf, - 'disabled': False, - 'mss4': '', - 'mss6': '' - } - - # Check if individual option is disabled - if conf.exists('disable'): - config['disabled'] = True - - # - # Get MSS value IPv4 - # - if conf.exists('adjust-mss'): - config['mss4'] = conf.return_value('adjust-mss') - - # We need a marker that a new iptables chain needs to be generated - if not opts['new_chain4']: - opts['new_chain4'] = True - - # - # Get MSS value IPv6 - # - if conf.exists('adjust-mss6'): - config['mss6'] = conf.return_value('adjust-mss6') - - # We need a marker that a new ip6tables chain needs to be generated - if not opts['new_chain6']: - opts['new_chain6'] = True - - # Append interface options to global list - opts['intf_opts'].append(config) - - return opts - -def verify(tcp): - # syntax verification is done via cli - return None - -def apply(tcp): - target = 'VYOS_FW_OPTIONS' - - # always cleanup iptables - call('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target)) - call('iptables --table mangle --flush {} >&/dev/null'.format(target)) - call('iptables --table mangle --delete-chain {} >&/dev/null'.format(target)) - - # always cleanup ip6tables - call('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target)) - call('ip6tables --table mangle --flush {} >&/dev/null'.format(target)) - call('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target)) - - # Setup new iptables rules - if tcp['new_chain4']: - call('iptables --table mangle --new-chain {} >&/dev/null'.format(target)) - call('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target)) - - for opts in tcp['intf_opts']: - intf = opts['intf'] - mss = opts['mss4'] - - # Check if this rule iis disabled - if opts['disabled']: - continue - - # adjust TCP MSS per interface - if mss: - call('iptables --table mangle --append {} --out-interface {} --protocol tcp ' - '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss)) - - # Setup new ip6tables rules - if tcp['new_chain6']: - call('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target)) - call('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target)) - - for opts in tcp['intf_opts']: - intf = opts['intf'] - mss = opts['mss6'] - - # Check if this rule iis disabled - if opts['disabled']: - continue - - # adjust TCP MSS per interface - if mss: - call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp ' - '--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss)) - - return None - -if __name__ == '__main__': - - try: - c = get_config() - verify(c) - apply(c) - except ConfigError as e: - print(e) - sys.exit(1) diff --git a/src/migration-scripts/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6 new file mode 100755 index 000000000..ccb86830a --- /dev/null +++ b/src/migration-scripts/firewall/5-to-6 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +# T3090: migrate "firewall options interface adjust-mss" to the +# individual interface. + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['firewall', 'options', 'interface'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +for interface in config.list_nodes(base): + if config.exists(base + [interface, 'disable']): + continue + + if config.exists(base + [interface, 'adjust-mss']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss']) + config.set(['interfaces', section, interface, 'ip', 'adjust-mss'], value=tmp) + + if config.exists(base + [interface, 'adjust-mss6']): + section = Section.section(interface) + tmp = config.return_value(base + [interface, 'adjust-mss6']) + config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) + +config.delete(['firewall', 'options']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From fd5990a019152dd6080fd134dba15928eb6535d1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 20 Aug 2021 16:18:24 +0200 Subject: wwan: T3620: remove superfluous import statement WWAN does no londer need to render any configuration files. --- src/conf_mode/interfaces-wwan.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 31c599145..faa5eb628 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -26,7 +26,6 @@ from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf from vyos.util import cmd from vyos.util import dict_search -from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() -- cgit v1.2.3 From 64c9fdef02323309e97b2bb682604ada52d651e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 20 Aug 2021 17:13:32 +0200 Subject: pppoe: T3090: migrate to vyos.ifconfig library to use the full potential Now that MSS clamping is done on the "per-interface" level the entire PPPoE stuff would have needed to get a full copy in GNU BASH for this or, participate in the common library. Add a new PPP ip-up script named 99-vyos-pppoe-callback which will call the vyos.ifconfig.PPPoEIf.update() function to configure everything as done with all other interfaces. This removes duplicated code for VRF assignment and route installation when a PPPoE interface is brought up or down. --- Makefile | 2 +- data/templates/pppoe/ipv6-up.script.tmpl | 37 ---------- data/templates/pppoe/peer.tmpl | 8 ++- debian/vyos-1x.install | 1 + python/vyos/ifconfig/pppoe.py | 110 ++++++++++++++++++++++++++++- src/conf_mode/interfaces-pppoe.py | 93 ++++++++++++++---------- src/etc/ppp/ip-up.d/99-vyos-pppoe-callback | 59 ++++++++++++++++ 7 files changed, 230 insertions(+), 80 deletions(-) create mode 100755 src/etc/ppp/ip-up.d/99-vyos-pppoe-callback (limited to 'src/conf_mode') diff --git a/Makefile b/Makefile index 0aa35d17d..2c79f545d 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ clean: .PHONY: test test: - set -e; python3 -m compileall -q -x '/vmware-tools/scripts/' . + set -e; python3 -m compileall -q -x '/vmware-tools/scripts/, /ppp/' . PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose .PHONY: sonar diff --git a/data/templates/pppoe/ipv6-up.script.tmpl b/data/templates/pppoe/ipv6-up.script.tmpl index 7e1bc33b4..da73cb4d5 100644 --- a/data/templates/pppoe/ipv6-up.script.tmpl +++ b/data/templates/pppoe/ipv6-up.script.tmpl @@ -7,43 +7,6 @@ if [ "$6" != "{{ ifname }}" ]; then exit fi -{% if ipv6 is defined and ipv6.address is defined and ipv6.address.autoconf is defined %} -# add some info to syslog -DIALER_PID=$(cat /var/run/{{ ifname }}.pid) -logger -t pppd[$DIALER_PID] "executing $0" -logger -t pppd[$DIALER_PID] "configuring interface {{ ifname }} via {{ source_interface }}" - -# Configure interface-specific Host/Router behaviour. -# Note: It is recommended to have the same setting on all interfaces; mixed -# router/host scenarios are rather uncommon. Possible values are: -# -# 0 Forwarding disabled -# 1 Forwarding enabled -# -echo 1 > /proc/sys/net/ipv6/conf/{{ ifname }}/forwarding - -# Accept Router Advertisements; autoconfigure using them. -# -# It also determines whether or not to transmit Router -# Solicitations. If and only if the functional setting is to -# accept Router Advertisements, Router Solicitations will be -# transmitted. Possible values are: -# -# 0 Do not accept Router Advertisements. -# 1 Accept Router Advertisements if forwarding is disabled. -# 2 Overrule forwarding behaviour. Accept Router Advertisements -# even if forwarding is enabled. -# -echo 2 > /proc/sys/net/ipv6/conf/{{ ifname }}/accept_ra - -# Autoconfigure addresses using Prefix Information in Router Advertisements. -echo 1 > /proc/sys/net/ipv6/conf/{{ ifname }}/autoconf -{% endif %} - -{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} -# Start wide dhcpv6 client -systemctl restart dhcp6c@{{ ifname }}.service -{% endif %} {% if default_route != 'none' %} # See https://phabricator.vyos.net/T2248 & T2220. Determine if we are enslaved diff --git a/data/templates/pppoe/peer.tmpl b/data/templates/pppoe/peer.tmpl index 0f78f9384..e8fda2cae 100644 --- a/data/templates/pppoe/peer.tmpl +++ b/data/templates/pppoe/peer.tmpl @@ -30,7 +30,7 @@ connect /bin/true noauth # Don't try to proxy ARP for the remote endpoint. User can set proxy -# arp entries up manually if they wish. More importantly, having +# arp entries up manually if they wish. More importantly, having # the "proxyarp" parameter set disables the "defaultroute" option. noproxyarp @@ -71,8 +71,14 @@ demand # passed to the ip-up.d/ip-down.s scripts which is required for VRF support. {% if 'auto' in default_route %} defaultroute +{{ 'defaultroute6' if ipv6 is defined }} {% elif 'force' in default_route %} defaultroute replacedefaultroute +{{ 'defaultroute6' if ipv6 is defined }} {% endif %} +{% else %} +nodefaultroute +noreplacedefaultroute +{{ 'nodefaultroute6' if ipv6 is defined }} {% endif %} diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 7ca568eff..d332e0d36 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -3,6 +3,7 @@ etc/dhcp etc/ipsec.d etc/netplug etc/opennhrp +etc/ppp etc/rsyslog.d etc/securetty etc/security diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 6acf7d1c7..9153863de 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors +# Copyright 2020-2021 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -14,6 +14,7 @@ # License along with this library. If not, see . from vyos.ifconfig.interface import Interface +from vyos.util import get_interface_config @Interface.register class PPPoEIf(Interface): @@ -26,7 +27,31 @@ class PPPoEIf(Interface): }, } - # stub this interface is created in the configure script + def _remove_routes(self, vrf=''): + # Always delete default routes when interface is removed + if vrf: + vrf = f'-c "vrf {vrf}"' + self._cmd(f'vtysh -c "conf t" {vrf} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"') + self._cmd(f'vtysh -c "conf t" {vrf} -c "no ipv6 route ::/0 {self.ifname} tag 210"') + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses and clear possible DHCP(v6) + client processes. + Example: + >>> from vyos.ifconfig import Interface + >>> i = Interface('pppoe0') + >>> i.remove() + """ + + tmp = get_interface_config(self.ifname) + vrf = '' + if 'master' in tmp: + self._remove_routes(tmp['master']) + + # remove bond master which places members in disabled state + super().remove() def _create(self): # we can not create this interface as it is managed outside @@ -35,3 +60,84 @@ class PPPoEIf(Interface): def _delete(self): # we can not create this interface as it is managed outside pass + + def del_addr(self, addr): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # remove old routes from an e.g. old VRF assignment + vrf = '' + if 'vrf_old' in config: + vrf = config['vrf_old'] + self._remove_routes(vrf) + + # DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do + # not require an 'address dhcpv6' CLI option as with other interfaces + if 'dhcpv6_options' in config and 'pd' in config['dhcpv6_options']: + self.set_dhcpv6(True) + else: + self.set_dhcpv6(False) + + super().update(config) + + if 'default_route' not in config or config['default_route'] == 'none': + return + + # + # Set default routes pointing to pppoe interface + # + vrf = '' + sed_opt = '^ip route' + + install_v4 = True + install_v6 = True + + # generate proper configuration string when VRFs are in use + if 'vrf' in config: + tmp = config['vrf'] + vrf = f'-c "vrf {tmp}"' + sed_opt = f'vrf {tmp}' + + if config['default_route'] == 'auto': + # only add route if there is no default route present + tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"') + for line in tmp.splitlines(): + line = line.lstrip() + if line.startswith('ip route 0.0.0.0/0'): + install_v4 = False + continue + + if 'ipv6' in config and line.startswith('ipv6 route ::/0'): + install_v6 = False + continue + + elif config['default_route'] == 'force': + # Force means that all static routes are replaced with the ones from this interface + tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"') + for line in tmp.splitlines(): + if self.ifname in line: + # It makes no sense to remove a route with our interface and the later re-add it. + # This will only make traffic disappear - which is a no-no! + continue + + line = line.lstrip() + if line.startswith('ip route 0.0.0.0/0'): + self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"') + + if 'ipv6' in config and line.startswith('ipv6 route ::/0'): + self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"') + + if install_v4: + self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210"') + if install_v6 and 'ipv6' in config: + self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210"') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 6c4c6c95b..584adc75e 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2021 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 @@ -22,12 +22,16 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed from vyos.configverify import verify_authentication from vyos.configverify import verify_source_interface +from vyos.configverify import verify_interface_exists from vyos.configverify import verify_vrf from vyos.configverify import verify_mtu_ipv6 +from vyos.ifconfig import PPPoEIf from vyos.template import render from vyos.util import call +from vyos.util import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -44,6 +48,32 @@ def get_config(config=None): base = ['interfaces', 'pppoe'] pppoe = get_interface_dict(conf, base) + # We should only terminate the PPPoE session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + tmp = leaf_node_changed(conf, ['access-concentrator']) + if tmp: pppoe.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['connect-on-demand']) + if tmp: pppoe.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['service-name']) + if tmp: pppoe.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['source-interface']) + if tmp: pppoe.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['vrf']) + # leaf_node_changed() returns a list, as VRF is a non-multi node, there + # will be only one list element + if tmp: pppoe.update({'vrf_old': tmp[0]}) + + tmp = leaf_node_changed(conf, ['authentication', 'user']) + if tmp: pppoe.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['authentication', 'password']) + if tmp: pppoe.update({'shutdown_required': {}}) + return pppoe def verify(pppoe): @@ -66,57 +96,42 @@ def generate(pppoe): # rendered into ifname = pppoe['ifname'] config_pppoe = f'/etc/ppp/peers/{ifname}' - script_pppoe_pre_up = f'/etc/ppp/ip-pre-up.d/1000-vyos-pppoe-{ifname}' - script_pppoe_ip_up = f'/etc/ppp/ip-up.d/1000-vyos-pppoe-{ifname}' - script_pppoe_ip_down = f'/etc/ppp/ip-down.d/1000-vyos-pppoe-{ifname}' - script_pppoe_ipv6_up = f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{ifname}' - config_wide_dhcp6c = f'/run/dhcp6c/dhcp6c.{ifname}.conf' - - config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, - script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c] if 'deleted' in pppoe or 'disable' in pppoe: - # stop DHCPv6-PD client - call(f'systemctl stop dhcp6c@{ifname}.service') - # Hang-up PPPoE connection - call(f'systemctl stop ppp@{ifname}.service') - - # Delete PPP configuration files - for file in config_files: - if os.path.exists(file): - os.unlink(file) + if os.path.exists(config_pppoe): + os.unlink(config_pppoe) return None # Create PPP configuration files - render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o755) - - # Create script for ip-pre-up.d - render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe, - permission=0o755) - # Create script for ip-up.d - render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe, - permission=0o755) - # Create script for ip-down.d - render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe, - permission=0o755) - # Create script for ipv6-up.d - render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, - permission=0o755) - - if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: - # ipv6.tmpl relies on ifname - this should be made consitent in the - # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe) + render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o640) return None def apply(pppoe): + ifname = pppoe['ifname'] if 'deleted' in pppoe or 'disable' in pppoe: - call('systemctl stop ppp@{ifname}.service'.format(**pppoe)) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') return None - call('systemctl restart ppp@{ifname}.service'.format(**pppoe)) + # reconnect should only be necessary when certain config options change, + # like ACS name, authentication, no-peer-dns, source-interface + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in pppoe): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.update(pppoe) return None diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback new file mode 100755 index 000000000..bb918a468 --- /dev/null +++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +# This is a Python hook script which is invoked whenever a PPPoE session goes +# "ip-up". It will call into our vyos.ifconfig library and will then execute +# common tasks for the PPPoE interface. The reason we have to "hook" this is +# that we can not create a pppoeX interface in advance in linux and then connect +# pppd to this already existing interface. + +from sys import argv +from sys import exit + +from syslog import syslog +from syslog import openlog +from syslog import LOG_PID +from syslog import LOG_INFO + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import PPPoEIf +from vyos.util import read_file + +# When the ppp link comes up, this script is called with the following +# parameters +# $1 the interface name used by pppd (e.g. ppp3) +# $2 the tty device name +# $3 the tty device speed +# $4 the local IP address for the interface +# $5 the remote IP address +# $6 the parameter specified by the 'ipparam' option to pppd + +if (len(argv) < 7): + exit(1) + +interface = argv[6] +dialer_pid = read_file(f'/var/run/{interface}.pid') + +openlog(ident=f'pppd[{dialer_pid}]', facility=LOG_INFO) +syslog('executing ' + argv[0]) + +conf = ConfigTreeQuery() +pppoe = conf.get_config_dict(['interfaces', 'pppoe', argv[6]], + get_first_key=True, key_mangling=('-', '_')) +pppoe['ifname'] = argv[6] + +p = PPPoEIf(pppoe['ifname']) +p.update(pppoe) -- cgit v1.2.3 From 27095042decb58fb53766c190bb60adcb69805ef Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 11:16:13 +0200 Subject: containers: T2216: add missing verify() step on environment variables A environment variable MUST always have a value specified. Non existing values will cause the following error: Traceback (most recent call last): File "/usr/libexec/vyos/conf_mode/containers.py", line 269, in apply(c) File "/usr/libexec/vyos/conf_mode/containers.py", line 224, in apply env_opt += " -e ".join(f"{k}={v['value']}" for k, v in container_config['environment'].items()) File "/usr/libexec/vyos/conf_mode/containers.py", line 224, in env_opt += " -e ".join(f"{k}={v['value']}" for k, v in container_config['environment'].items()) KeyError: 'value' --- src/conf_mode/containers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 7544bd840..b573df889 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -102,7 +102,6 @@ def verify(container): if len(container_config['network']) > 1: raise ConfigError(f'Only one network can be specified for container "{name}"!') - # Check if the specified container network exists network_name = list(container_config['network'])[0] if network_name not in container['network']: @@ -127,6 +126,10 @@ def verify(container): if ip_address(address) == ip_network(network)[1]: raise ConfigError(f'Address "{address}" reserved for the container engine!') + if 'environment' in container_config: + for var, cfg in container_config['environment'].items(): + if 'value' not in cfg: + raise ConfigError(f'Environment variable {var} has no value assigned!') # Container image is a mandatory option if 'image' not in container_config: -- cgit v1.2.3 From 1f6746c44c5349a35847abfb410d3826a4e7ca99 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 11:48:00 +0200 Subject: containers: T2216: xml: impove help string for address command --- interface-definitions/containers.xml.in | 2 +- src/conf_mode/containers.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 29762c171..030980dba 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -60,7 +60,7 @@ Set IPv4 static address to container (optional) ipv4 - IPv4 address (x.x.x.1 reserved) + IPv4 address diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index b573df889..23f17ab55 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -124,7 +124,8 @@ def verify(container): # We can not use the first IP address of a network prefix as this is used by podman if ip_address(address) == ip_network(network)[1]: - raise ConfigError(f'Address "{address}" reserved for the container engine!') + raise ConfigError(f'IP address "{address}" can not be used for a container, '\ + 'reserved for the container engine!') if 'environment' in container_config: for var, cfg in container_config['environment'].items(): -- cgit v1.2.3 From e71a98562aabea53cb9b5e1958398e1e674490bb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 11:48:47 +0200 Subject: containers: T2216: add CLI commands to specify restart behavior and memory usage A container is limited to 256MB memory by default and will always restart on failure. --- interface-definitions/containers.xml.in | 42 +++++++++++++++++++++++++++++++++ src/conf_mode/containers.py | 22 +++++++++++++---- 2 files changed, 59 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 030980dba..419802866 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -47,6 +47,24 @@ Image name in the hub-registry + + + Constrain the memory available to a container (default: 256MB) + + u32:0 + Unlimited + + + u32:1-16384 + Container memory in megabytes (MB) + + + + + Container memory must be in range 0 to 16384 MB + + 256 + Attach user defined network to container @@ -119,6 +137,30 @@ + + + Mount a volume into the container + + no on-failure always + + + no + Do not restart containers on exit + + + on-failure + Restart containers when they exit with a non-zero exit code, retrying indefinitely (default) + + + always + Restart containers when they exit, regardless of status, retrying indefinitely + + + ^(no|on-failure|always)$ + + + on-failure + Mount a volume into the container diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 23f17ab55..fd0c5e52d 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -79,8 +79,17 @@ def get_config(config=None): # 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) + # container base default values can not be merged here - remove and add them later + if 'name' in default_values: + del default_values['name'] container = dict_merge(default_values, container) + # Merge per-container default values + if 'name' in container: + default_values = defaults(base + ['name']) + for name in container['name']: + container['name'][name] = dict_merge(default_values, container['name'][name]) + # Delete container network, delete containers tmp = node_changed(conf, ['container', 'network']) if tmp: container.update({'net_remove' : tmp}) @@ -216,6 +225,8 @@ def apply(container): # Check if the container has already been created if not container_exists(name): image = container_config['image'] + memory = container_config['memory'] + restart = container_config['restart'] # Currently the best way to run a command and immediately print stdout print(os.system(f'podman pull {image}')) @@ -242,19 +253,20 @@ def apply(container): # Bind volume volume = '' if 'volume' in container_config: - for vol in container_config['volume']: - svol = container_config['volume'][vol]['source'] - dvol = container_config['volume'][vol]['destination'] + for vol, vol_config in container_config['volume']: + svol = vol_config['source'] + dvol = vol_config['destination'] volume += f' -v {svol}:{dvol}' + container_base_cmd = f'podman run -dit --name {name} --memory {memory} --restart {restart} {port} {volume} {env_opt} {image}' if 'allow_host_networks' in container_config: - _cmd(f'podman run -dit --name {name} --net host {port} {volume} {env_opt} {image}') + _cmd(f'{container_base_cmd} --net host') else: for network in container_config['network']: ipparam = '' if 'address' in container_config['network'][network]: ipparam = '--ip ' + container_config['network'][network]['address'] - _cmd(f'podman run --name {name} -dit --net {network} {ipparam} {port} {volume} {env_opt} {image}') + _cmd(f'{container_base_cmd} --net {network} {ipparam}') # Else container is already created. Just start it. # It's needed after reboot. -- cgit v1.2.3 From e0b2453bd0399d387f4a4d0c4f2e12509249db27 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 14:03:36 +0200 Subject: containers: T2216: restructure container_base_cmd to have image name at the end --- src/conf_mode/containers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index fd0c5e52d..7aea5f432 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -233,8 +233,8 @@ def apply(container): # Check/set environment options "-e foo=bar" env_opt = '' if 'environment' in container_config: - env_opt = '-e ' - env_opt += " -e ".join(f"{k}={v['value']}" for k, v in container_config['environment'].items()) + for k, v in container_config['environment'].items(): + env_opt += f" -e \"{k}={v['value']}\"" # Publish ports port = '' @@ -258,15 +258,15 @@ def apply(container): dvol = vol_config['destination'] volume += f' -v {svol}:{dvol}' - container_base_cmd = f'podman run -dit --name {name} --memory {memory} --restart {restart} {port} {volume} {env_opt} {image}' + container_base_cmd = f'podman run -dit --name {name} --memory {memory}m --restart {restart} {port} {volume} {env_opt}' if 'allow_host_networks' in container_config: - _cmd(f'{container_base_cmd} --net host') + _cmd(f'{container_base_cmd} --net host {image}') else: for network in container_config['network']: ipparam = '' if 'address' in container_config['network'][network]: ipparam = '--ip ' + container_config['network'][network]['address'] - _cmd(f'{container_base_cmd} --net {network} {ipparam}') + _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}') # Else container is already created. Just start it. # It's needed after reboot. -- cgit v1.2.3 From c270fe05596c95eca20a96a8c7e561d28bfc9a82 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 15:18:43 +0200 Subject: container: T2216: no need to query container status As VyOS CLI is the only truth for dealing with containers we do not need to query if a container is running, exists or what so ever. We simply always restart it if something changes and do not rely on the underlaying Linux status. If a users does container stuff under the hood - it will be overridden. --- src/conf_mode/containers.py | 137 +++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 79 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 7aea5f432..12e6953b5 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -23,8 +23,9 @@ from ipaddress import ip_network from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed +from vyos.util import call from vyos.util import cmd -from vyos.util import popen +from vyos.util import run from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -41,26 +42,6 @@ def _cmd(command): print(command) return cmd(command) -# Container management functions -def container_exists(name): - ''' - https://docs.podman.io/en/latest/_static/api.html#operation/ContainerExistsLibpod - Check if container exists. Response codes. - 204 - container exists - 404 - no such container - ''' - tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/exists'") - # If container exists it return status code "0" - code can not be displayed - return (tmp == "") - -def container_status(name): - ''' - https://docs.podman.io/en/latest/_static/api.html#operation/ContainerInspectLibpod - ''' - tmp = _cmd(f"curl --unix-socket /run/podman/podman.sock 'http://d/v3.0.0/libpod/containers/{name}/json'") - data = json.loads(tmp) - return data['State']['Status'] - def ctnr_network_exists(name): # Check explicit name for network, returns True if network exists c = _cmd(f'podman network ls --quiet --filter name=^{name}$') @@ -92,7 +73,7 @@ def get_config(config=None): # Delete container network, delete containers tmp = node_changed(conf, ['container', 'network']) - if tmp: container.update({'net_remove' : tmp}) + if tmp: container.update({'network_remove' : tmp}) tmp = node_changed(conf, ['container', 'name']) if tmp: container.update({'container_remove' : tmp}) @@ -173,8 +154,8 @@ def verify(container): # A network attached to a container can not be deleted - if {'net_remove', 'name'} <= set(container): - for network in container['net_remove']: + if {'network_remove', 'name'} <= set(container): + for network in container['network_remove']: for container, container_config in container['name'].items(): if 'network' in container_config and network in container_config['network']: raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!') @@ -196,14 +177,13 @@ def apply(container): # Option "--force" allows to delete containers with any status if 'container_remove' in container: for name in container['container_remove']: - if container_status(name) == 'running': - _cmd(f'podman stop {name}') - _cmd(f'podman rm --force {name}') + call(f'podman stop {name}') + call(f'podman rm --force {name}') # Delete old networks if needed - if 'net_remove' in container: - for network in container['net_remove']: - _cmd(f'podman network rm {network}') + if 'network_remove' in container: + for network in container['network_remove']: + call(f'podman network rm --force {network}') # Add network if 'network' in container: @@ -223,55 +203,54 @@ def apply(container): if 'name' in container: for name, container_config in container['name'].items(): # Check if the container has already been created - if not container_exists(name): - image = container_config['image'] - memory = container_config['memory'] - restart = container_config['restart'] - # Currently the best way to run a command and immediately print stdout - print(os.system(f'podman pull {image}')) - - # Check/set environment options "-e foo=bar" - env_opt = '' - if 'environment' in container_config: - for k, v in container_config['environment'].items(): - env_opt += f" -e \"{k}={v['value']}\"" - - # Publish ports - port = '' - if 'port' in container_config: - protocol = '' - for portmap in container_config['port']: - if 'protocol' in container_config['port'][portmap]: - protocol = container_config['port'][portmap]['protocol'] - protocol = f'/{protocol}' - else: - protocol = '/tcp' - sport = container_config['port'][portmap]['source'] - dport = container_config['port'][portmap]['destination'] - port += f' -p {sport}:{dport}{protocol}' - - # Bind volume - volume = '' - if 'volume' in container_config: - for vol, vol_config in container_config['volume']: - svol = vol_config['source'] - dvol = vol_config['destination'] - volume += f' -v {svol}:{dvol}' - - container_base_cmd = f'podman run -dit --name {name} --memory {memory}m --restart {restart} {port} {volume} {env_opt}' - if 'allow_host_networks' in container_config: - _cmd(f'{container_base_cmd} --net host {image}') - else: - for network in container_config['network']: - ipparam = '' - if 'address' in container_config['network'][network]: - ipparam = '--ip ' + container_config['network'][network]['address'] - _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}') - - # Else container is already created. Just start it. - # It's needed after reboot. - elif container_status(name) != 'running': - _cmd(f'podman start {name}') + image = container_config['image'] + memory = container_config['memory'] + restart = container_config['restart'] + + # Check if requested container image exists locally. If it does not, we + # pull it. print() is the best way to have a good response from the + # polling process to the user to display progress. If the image exists + # locally, a user can update it running `update container image ` + tmp = run(f'podman image exists {image}') + if tmp != 0: print(os.system(f'podman pull {image}')) + + # Check/set environment options "-e foo=bar" + env_opt = '' + if 'environment' in container_config: + for k, v in container_config['environment'].items(): + env_opt += f" -e \"{k}={v['value']}\"" + + # Publish ports + port = '' + if 'port' in container_config: + protocol = '' + for portmap in container_config['port']: + if 'protocol' in container_config['port'][portmap]: + protocol = container_config['port'][portmap]['protocol'] + protocol = f'/{protocol}' + else: + protocol = '/tcp' + sport = container_config['port'][portmap]['source'] + dport = container_config['port'][portmap]['destination'] + port += f' -p {sport}:{dport}{protocol}' + + # Bind volume + volume = '' + if 'volume' in container_config: + for vol, vol_config in container_config['volume']: + svol = vol_config['source'] + dvol = vol_config['destination'] + volume += f' -v {svol}:{dvol}' + + container_base_cmd = f'podman run --detach --interactive --tty --replace --memory {memory}m --restart {restart} --name {name} {port} {volume} {env_opt}' + if 'allow_host_networks' in container_config: + _cmd(f'{container_base_cmd} --net host {image}') + else: + for network in container_config['network']: + ipparam = '' + if 'address' in container_config['network'][network]: + ipparam = '--ip ' + container_config['network'][network]['address'] + _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}') return None -- cgit v1.2.3 From b48f7688f758921904fb6ef5185080d97c1e3920 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 15:50:48 +0200 Subject: container: T2216: bugfix ValueError when assembling volumes A call to .items() was missing that triggered the following error: ValueError: too many values to unpack (expected 2) --- src/conf_mode/containers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 12e6953b5..2c4861449 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -237,12 +237,14 @@ def apply(container): # Bind volume volume = '' if 'volume' in container_config: - for vol, vol_config in container_config['volume']: + for vol, vol_config in container_config['volume'].items(): svol = vol_config['source'] dvol = vol_config['destination'] volume += f' -v {svol}:{dvol}' - container_base_cmd = f'podman run --detach --interactive --tty --replace --memory {memory}m --restart {restart} --name {name} {port} {volume} {env_opt}' + container_base_cmd = f'podman run --detach --interactive --tty --replace ' \ + f'--memory {memory}m --memory-swap 0 --restart {restart} ' \ + f'--name {name} {port} {volume} {env_opt}' if 'allow_host_networks' in container_config: _cmd(f'{container_base_cmd} --net host {image}') else: -- cgit v1.2.3 From 8774ef9377610559d15071acec5442180c660924 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 15:59:00 +0200 Subject: container: T2216: verify() volume paths Volumes must have both a source and destination path specified. Also the source path must exist on the current system. --- src/conf_mode/containers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 2c4861449..97e84a5bb 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -122,6 +122,18 @@ def verify(container): if 'value' not in cfg: raise ConfigError(f'Environment variable {var} has no value assigned!') + if 'volume' in container_config: + for volume, volume_config in container_config['volume'].items(): + if 'source' not in volume_config: + raise ConfigError(f'Volume "{volume}" has no source path configured!') + + if 'destination' not in volume_config: + raise ConfigError(f'Volume "{volume}" has no destination path configured!') + + source = volume_config['source'] + if not os.path.exists(source): + raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!') + # Container image is a mandatory option if 'image' not in container_config: raise ConfigError(f'Container image for "{name}" is mandatory!') -- cgit v1.2.3 From 7762e74d928e9f23360798a21c8dd970579365b6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 16:57:38 +0200 Subject: container: T2216: add option to "disable" a container --- interface-definitions/containers.xml.in | 1 + src/conf_mode/containers.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index 286b4942a..d990e41a3 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -22,6 +22,7 @@ #include + #include Add custom environment variables diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 97e84a5bb..5b863fa03 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -214,8 +214,15 @@ def apply(container): # Add container if 'name' in container: for name, container_config in container['name'].items(): - # Check if the container has already been created image = container_config['image'] + + if 'disable' in container_config: + # check if there is a container by that name running + tmp = _cmd('podman ps -a --format "{{.Names}}"') + if name in tmp: + _cmd(f'podman stop {name}') + continue + memory = container_config['memory'] restart = container_config['restart'] -- cgit v1.2.3 From 209ce3d9b6fb09626a7abe3540b888566b739de8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 23 Aug 2021 20:51:19 +0200 Subject: container: T3769: when container networks are used, always bridge the networks As VyOS is a network operation system with bridging and NATing available from the VyOS CLI, it makes no sense to let podman do it's own sort of "NAT". If one really want's to NAT into a container, use the VyOS CLI to do so. If you wan't to bridge your networks, use the VyOS CLI to do so. --- src/conf_mode/containers.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 5b863fa03..78664dfd9 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -26,6 +26,8 @@ from vyos.configdict import node_changed from vyos.util import call from vyos.util import cmd from vyos.util import run +from vyos.util import read_file +from vyos.util import write_file from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -42,7 +44,7 @@ def _cmd(command): print(command) return cmd(command) -def ctnr_network_exists(name): +def network_exists(name): # Check explicit name for network, returns True if network exists c = _cmd(f'podman network ls --quiet --filter name=^{name}$') return bool(c) @@ -201,7 +203,7 @@ def apply(container): if 'network' in container: for network, network_config in container['network'].items(): # Check if the network has already been created - if not ctnr_network_exists(network) and 'prefix' in network_config: + if not network_exists(network) and 'prefix' in network_config: tmp = f'podman network create {network}' # we can not use list comprehension here as the --ipv6 option # must immediately follow the specified subnet!!! @@ -211,6 +213,18 @@ def apply(container): tmp += ' --ipv6' _cmd(tmp) + # Disable masquerading and use traditional bridging so VyOS + # can control firewalling/NAT by the real VyOS CLI + cni_network_config = f'/etc/cni/net.d/{network}.conflist' + tmp = read_file(cni_network_config) + config = json.loads(tmp) + if 'plugins' in config: + for count in range(0, len(config['plugins'])): + if 'ipMasq' in config['plugins'][count]: + config['plugins'][count]['ipMasq'] = False + + write_file(cni_network_config, json.dumps(config, indent=4)) + # Add container if 'name' in container: for name, container_config in container['name'].items(): -- cgit v1.2.3 From 10ab1acc801df73b2dfee1f2cfd059424d8894d5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 24 Aug 2021 10:17:18 +0200 Subject: container: T3769: disable bridge "hairpinMode" mode After commit 209ce3d9 ("container: T3769: when container networks are used, always bridge the networks") IP masquerading (NAT) was disabled. No need to keep the haipin flag. --- src/conf_mode/containers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 78664dfd9..85fc14015 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -222,6 +222,8 @@ def apply(container): for count in range(0, len(config['plugins'])): if 'ipMasq' in config['plugins'][count]: config['plugins'][count]['ipMasq'] = False + if 'hairpinMode' in config['plugins'][count]: + config['plugins'][count]['hairpinMode'] = False write_file(cni_network_config, json.dumps(config, indent=4)) -- cgit v1.2.3 From a98b0d886f522ca9620182bd0f1a2f73a905f35a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 24 Aug 2021 12:26:42 +0200 Subject: container: T3769: remove container when marked as "disable" --- src/conf_mode/containers.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 85fc14015..32320a4b2 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -237,6 +237,7 @@ def apply(container): tmp = _cmd('podman ps -a --format "{{.Names}}"') if name in tmp: _cmd(f'podman stop {name}') + _cmd(f'podman rm --force {name}') continue memory = container_config['memory'] -- cgit v1.2.3 From 4523e9c897b3fa8d12c1b16c830c01820fee5583 Mon Sep 17 00:00:00 2001 From: zsdc Date: Thu, 26 Aug 2021 18:15:36 +0300 Subject: wireguard: T3763: Added check for listening port availability Each wireguard interface requires a unique port for in and out connections. This commit adds the new `vyos.util` function - `check_port_availability`, and uses it to be sure that a port that is planned to be used for wireguard interface is truly available and not used by any other services (not only other wireguard interfaces). --- python/vyos/util.py | 39 +++++++++++++++++++++++++++++++++++ src/conf_mode/interfaces-wireguard.py | 5 +++++ 2 files changed, 44 insertions(+) (limited to 'src/conf_mode') diff --git a/python/vyos/util.py b/python/vyos/util.py index 8af46a6ee..fc2834a97 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -819,3 +819,42 @@ def is_systemd_service_running(service): Copied from: https://unix.stackexchange.com/a/435317 """ tmp = cmd(f'systemctl show --value -p SubState {service}') return bool((tmp == 'running')) + +def check_port_availability(ipaddress, port, protocol): + """ + Check if port is available and not used by any service + Return False if a port is busy or IP address does not exists + Should be used carefully for services that can start listening + dynamically, because IP address may be dynamic too + """ + from socketserver import TCPServer, UDPServer + from ipaddress import ip_address + + # verify arguments + try: + ipaddress = ip_address(ipaddress).compressed + except: + print(f'The {ipaddress} is not a valid IPv4 or IPv6 address') + return + if port not in range(1, 65536): + print(f'The port number {port} is not in the 1-65535 range') + return + if protocol not in ['tcp', 'udp']: + print( + f'The protocol {protocol} is not supported. Only tcp and udp are allowed' + ) + return + + # check port availability + try: + if protocol == 'tcp': + server = TCPServer((ipaddress, port), None, bind_and_activate=True) + if protocol == 'udp': + server = UDPServer((ipaddress, port), None, bind_and_activate=True) + server.server_close() + return True + except: + print( + f'The {protocol} port {port} on the {ipaddress} is busy or unavailable' + ) + return False diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 4c566a5ad..ad3ddcba2 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -30,6 +30,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod +from vyos.util import check_port_availability from vyos import ConfigError from vyos import airbag airbag.enable() @@ -73,6 +74,10 @@ def verify(wireguard): if 'peer' not in wireguard: raise ConfigError('At least one Wireguard peer is required!') + if 'port' in wireguard and check_port_availability( + '0.0.0.0', int(wireguard['port']), 'udp') is not True: + raise ConfigError('The port cannot be used for the interface') + # run checks on individual configured WireGuard peer for tmp in wireguard['peer']: peer = wireguard['peer'][tmp] -- cgit v1.2.3 From 9471a9bac452c2c5600ef5d91dd842c8e084e8d6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 26 Aug 2021 18:17:31 +0200 Subject: ipsec: T1210: support road-warrior IP assignment via RADIUS Framed-IP-Address Extended CLI command: "set vpn ipsec remote-access connection rw pool" with a "radius" option. --- interface-definitions/vpn_ipsec.xml.in | 12 ++++++++++-- src/conf_mode/vpn_ipsec.py | 24 +++++++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index b28c86ae6..b0dba4bce 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -771,11 +771,19 @@ Pool name used for IP address assignments vpn ipsec remote-access pool - dhcp + dhcp radius txt - Pool name + Name of predefined IP pool + + + dhcp + Forward requests for virtual IP addresses to a DHCP server + + + radius + Forward requests for virtual IP addresses to a RADIUS server diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index d3065fc47..ff6090e22 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -286,20 +286,34 @@ def verify(ipsec): if 'pre_shared_secret' not in ra_conf['authentication']: raise ConfigError(f"Missing pre-shared-key on {name} remote-access config") + if 'client_mode' not in ra_conf['authentication']: + raise ConfigError('Client authentication method is required!') - if 'client_mode' in ra_conf['authentication']: - if ra_conf['authentication']['client_mode'] == 'eap-radius': - if 'radius' not in ipsec['remote_access'] or 'server' not in ipsec['remote_access']['radius'] or len(ipsec['remote_access']['radius']['server']) == 0: - raise ConfigError('RADIUS authentication requires at least one server') + if dict_search('authentication.client_mode', ra_conf) == 'eap-radius': + if dict_search('remote_access.radius.server', ipsec) == None: + raise ConfigError('RADIUS authentication requires at least one server') if 'pool' in ra_conf: + if {'dhcp', 'radius'} <= set(ra_conf['pool']): + raise ConfigError(f'Can not use both DHCP and RADIUS for address allocation '\ + f'at the same time for "{name}"!') + if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1: - raise ConfigError(f'Can not use both DHCP and a predefined address pool for "{name}"!') + raise ConfigError(f'Can not use DHCP and a predefined address pool for "{name}"!') + + if 'radius' in ra_conf['pool'] and len(ra_conf['pool']) > 1: + raise ConfigError(f'Can not use RADIUS and a predefined address pool for "{name}"!') for pool in ra_conf['pool']: if pool == 'dhcp': if dict_search('remote_access.dhcp.server', ipsec) == None: raise ConfigError('IPSec DHCP server is not configured!') + elif pool == 'radius': + if dict_search('remote_access.radius.server', ipsec) == None: + raise ConfigError('IPSec RADIUS server is not configured!') + + if dict_search('authentication.client_mode', ra_conf) != 'eap-radius': + raise ConfigError('RADIUS IP pool requires eap-radius client authentication!') elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']: raise ConfigError(f'Requested pool "{pool}" does not exist!') -- cgit v1.2.3 From eb11d4b688d883d0c1d150b00eee40b54df42b32 Mon Sep 17 00:00:00 2001 From: zsdc Date: Thu, 26 Aug 2021 23:18:29 +0300 Subject: vyos.util: T3763: Optimized the check_port_availability function `print` was removed or replaced to `ValueError`, where possible. --- python/vyos/util.py | 12 +++--------- src/conf_mode/interfaces-wireguard.py | 9 ++++++--- 2 files changed, 9 insertions(+), 12 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/util.py b/python/vyos/util.py index fc2834a97..93a2f6640 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -834,16 +834,13 @@ def check_port_availability(ipaddress, port, protocol): try: ipaddress = ip_address(ipaddress).compressed except: - print(f'The {ipaddress} is not a valid IPv4 or IPv6 address') - return + raise ValueError(f'The {ipaddress} is not a valid IPv4 or IPv6 address') if port not in range(1, 65536): - print(f'The port number {port} is not in the 1-65535 range') - return + raise ValueError(f'The port number {port} is not in the 1-65535 range') if protocol not in ['tcp', 'udp']: - print( + raise ValueError( f'The protocol {protocol} is not supported. Only tcp and udp are allowed' ) - return # check port availability try: @@ -854,7 +851,4 @@ def check_port_availability(ipaddress, port, protocol): server.server_close() return True except: - print( - f'The {protocol} port {port} on the {ipaddress} is busy or unavailable' - ) return False diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index ad3ddcba2..68181465e 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -74,9 +74,12 @@ def verify(wireguard): if 'peer' not in wireguard: raise ConfigError('At least one Wireguard peer is required!') - if 'port' in wireguard and check_port_availability( - '0.0.0.0', int(wireguard['port']), 'udp') is not True: - raise ConfigError('The port cannot be used for the interface') + listen_port = int(wireguard['port']) + if 'port' in wireguard and check_port_availability('0.0.0.0', listen_port, + 'udp') is not True: + raise ConfigError( + f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface' + ) # run checks on individual configured WireGuard peer for tmp in wireguard['peer']: -- cgit v1.2.3 From 91a4d57d429719cbb35fe38f31e1889645a5579a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 29 Aug 2021 10:55:46 +0200 Subject: isis: T3783: bugfix configuring spf-delay-ietf Mandatory FRR options for spf-delay-ietf did not get rendered in the Jinja2 template. --- data/templates/frr/isisd.frr.tmpl | 2 +- smoketest/scripts/cli/test_protocols_isis.py | 53 ++++++++++++++++++++++++++++ src/conf_mode/containers.py | 16 +++++++++ src/conf_mode/protocols_isis.py | 2 +- 4 files changed, 71 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index 6cfa076d0..51ac40060 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -100,7 +100,7 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% endif %} {% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.init_delay is not none %} - spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} + spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} short-delay {{ spf_delay_ietf.short_delay }} long-delay {{ spf_delay_ietf.long_delay }} holddown {{ spf_delay_ietf.holddown }} time-to-learn {{ spf_delay_ietf.time_to_learn }} {% endif %} {% if area_password is defined and area_password is not none %} {% if area_password.md5 is defined and area_password.md5 is not none %} diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 9b6d4a4ec..8170f2b56 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -199,5 +199,58 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area-password clear {password}', tmp) + def test_isis_06_spf_delay(self): + self.isis_base_config() + + network = 'point-to-point' + holddown = '10' + init_delay = '50' + long_delay = '200' + short_delay = '100' + time_to_learn = '75' + + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'network', network]) + + self.cli_set(base_path + ['spf-delay-ietf', 'holddown', holddown]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'init-delay', init_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'long-delay', long_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'long-delay', long_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['spf-delay-ietf', 'short-delay', short_delay]) + # verify() - All types of spf-delay must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['spf-delay-ietf', 'time-to-learn', time_to_learn]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp) + + for interface in self._interfaces: + tmp = self.getFRRconfig(f'interface {interface}') + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' isis network {network}', tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 32320a4b2..3a93bf062 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -19,6 +19,7 @@ import json from ipaddress import ip_address from ipaddress import ip_network +from time import sleep from vyos.config import Config from vyos.configdict import dict_merge @@ -28,6 +29,9 @@ from vyos.util import cmd from vyos.util import run from vyos.util import read_file from vyos.util import write_file +from vyos.util import is_systemd_service_active +from vyos.util import is_systemd_service_running + from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -199,6 +203,18 @@ def apply(container): for network in container['network_remove']: call(f'podman network rm --force {network}') + service_name = 'podman.service' + if 'network' in container or 'name' in container: + # Start podman if it's required and not yet running + if not is_systemd_service_active(service_name): + _cmd(f'systemctl start {service_name}') + # Wait for podman to be running + while not is_systemd_service_running(service_name): + sleep(0.250) + else: + _cmd(f'systemctl stop {service_name}') + + # Add network if 'network' in container: for network, network_config in container['network'].items(): diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 4cf0312e9..4505e2496 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -148,7 +148,7 @@ def verify(isis): exist_timers = set(required_timers).difference(set(exist_timers)) if len(exist_timers) > 0: - raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-')) + raise ConfigError('All types of spf-delay must be configured. Missing: ' + ', '.join(exist_timers).replace('_', '-')) # If Redistribute set, but level don't set if 'redistribute' in isis: -- cgit v1.2.3 From 40bfaed4d1d427c33157136026944df80e02a5b6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 29 Aug 2021 12:08:22 +0200 Subject: ospf: T3236: add possibility to redistribute "table" Add new CLI command: * "set protocols ospf redistribute table " --- data/templates/frr/ospfd.frr.tmpl | 10 ++++++++-- interface-definitions/include/ospf/metric-type.xml.i | 2 +- .../include/ospf/protocol-common-config.xml.i | 17 +++++++++++++++++ src/conf_mode/protocols_ospf.py | 14 +++++++++++++- 4 files changed, 39 insertions(+), 4 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index be39519c3..90a6bbd56 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -172,8 +172,14 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endfor %} {% endif %} {% if redistribute is defined and redistribute is not none %} -{% for protocol, options in redistribute.items() %} - redistribute {{ protocol }} {{ 'metric ' + options.metric if options.metric is defined }} {{ 'metric-type ' + options.metric_type if options.metric_type is defined }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% for protocol, protocols_options in redistribute.items() %} +{% if protocol == 'table' %} +{% for table, table_options in protocols_options.items() %} + redistribute {{ protocol }} {{ table }} {{ 'metric ' + table_options.metric if table_options.metric is defined }} {{ 'metric-type ' + table_options.metric_type if table_options.metric_type is defined }} {{ 'route-map ' + table_options.route_map if table_options.route_map is defined }} +{% endfor %} +{% else %} + redistribute {{ protocol }} {{ 'metric ' + protocols_options.metric if protocols_options.metric is defined }} {{ 'metric-type ' + protocols_options.metric_type if protocols_options.metric_type is defined }} {{ 'route-map ' + protocols_options.route_map if protocols_options.route_map is defined }} +{% endif %} {% endfor %} {% endif %} {% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} diff --git a/interface-definitions/include/ospf/metric-type.xml.i b/interface-definitions/include/ospf/metric-type.xml.i index 83dc24909..ef9fd8ac0 100644 --- a/interface-definitions/include/ospf/metric-type.xml.i +++ b/interface-definitions/include/ospf/metric-type.xml.i @@ -4,7 +4,7 @@ OSPF metric type for default routes (default: 2) u32:1-2 - Metric type for default routes + Set OSPF External Type 1/2 metrics diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index 546516a80..d8556ebf5 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -693,6 +693,23 @@ #include + + + Redistribute non-main Kernel Routing Table + + protocols static table + + + u32:1-200 + Policy route table number + + + + #include + #include + #include + + diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 06a29106d..92532fcb5 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -87,7 +87,13 @@ def get_config(config=None): del default_values['area']['area_type']['nssa'] if 'mpls_te' not in ospf: del default_values['mpls_te'] - for protocol in ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: + + for protocol in ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']: + # table is a tagNode thus we need to clean out all occurances for the + # default values and load them in later individually + if protocol == 'table': + del default_values['redistribute']['table'] + continue if dict_search(f'redistribute.{protocol}', ospf) is None: del default_values['redistribute'][protocol] @@ -127,6 +133,12 @@ def get_config(config=None): ospf['interface'][interface] = dict_merge(default_values, ospf['interface'][interface]) + if 'redistribute' in ospf and 'table' in ospf['redistribute']: + default_values = defaults(base + ['redistribute', 'table']) + for table in ospf['redistribute']['table']: + ospf['redistribute']['table'][table] = dict_merge(default_values, + ospf['redistribute']['table'][table]) + # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). # -- cgit v1.2.3 From 26f6168626f79ebc3e027ecf5f77d79a6337d97c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 29 Aug 2021 12:09:12 +0200 Subject: ospf: T3236: remove debug print() statement --- src/conf_mode/protocols_ospf.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 92532fcb5..6ccda2e5a 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -115,7 +115,6 @@ def get_config(config=None): default_values = defaults(base + ['area', 'virtual-link']) for area, area_config in ospf['area'].items(): if 'virtual_link' in area_config: - print(default_values) for virtual_link in area_config['virtual_link']: ospf['area'][area]['virtual_link'][virtual_link] = dict_merge( default_values, ospf['area'][area]['virtual_link'][virtual_link]) -- cgit v1.2.3 From c78daaf0f93937a7ecac139c45c5c81f7fcee81f Mon Sep 17 00:00:00 2001 From: zsdc Date: Sun, 29 Aug 2021 15:50:24 +0300 Subject: wireguard: T3763: Fixed uninitialized port issue The commit fixes the problem, when port availability check is triggered even if a port for WireGuard interface is not defined (randomized port, default behavior). --- src/conf_mode/interfaces-wireguard.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 68181465e..9baf5b6e9 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -74,12 +74,12 @@ def verify(wireguard): if 'peer' not in wireguard: raise ConfigError('At least one Wireguard peer is required!') - listen_port = int(wireguard['port']) - if 'port' in wireguard and check_port_availability('0.0.0.0', listen_port, - 'udp') is not True: - raise ConfigError( - f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface' - ) + if 'port' in wireguard: + listen_port = int(wireguard['port']) + if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: + raise ConfigError( + f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface' + ) # run checks on individual configured WireGuard peer for tmp in wireguard['peer']: -- cgit v1.2.3 From 8d0207f87cf692458b688527022c8d841ec72904 Mon Sep 17 00:00:00 2001 From: zsdc Date: Sun, 29 Aug 2021 16:13:45 +0300 Subject: wireguard: T3763: The port availability check fix Check a port availability only if it was changed in current commit. This should protect from fail-positive errors when other parameters change for an interface. --- src/conf_mode/interfaces-wireguard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 9baf5b6e9..da64dd076 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -47,6 +47,9 @@ def get_config(config=None): base = ['interfaces', 'wireguard'] wireguard = get_interface_dict(conf, base) + # Check if a port was changed + wireguard['port_changed'] = leaf_node_changed(conf, ['port']) + # Determine which Wireguard peer has been removed. # Peers can only be removed with their public key! dict = {} @@ -74,7 +77,7 @@ def verify(wireguard): if 'peer' not in wireguard: raise ConfigError('At least one Wireguard peer is required!') - if 'port' in wireguard: + if 'port' in wireguard and wireguard['port_changed']: listen_port = int(wireguard['port']) if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: raise ConfigError( -- cgit v1.2.3 From 5c29377fa91595088118419275f6d05b1fbfbd1d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 30 Aug 2021 15:43:02 +0000 Subject: tunnel: T3786: Add checks for source any and not key --- src/conf_mode/interfaces-tunnel.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 294da8ef9..616a2e23c 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -94,6 +94,12 @@ def verify(tunnel): if 'direction' not in tunnel['parameters']['erspan']: raise ConfigError('ERSPAN version 2 requires direction to be set!') + # If tunnel source address any and key not set + if tunnel['encapsulation'] in ['gre'] and \ + tunnel['source_address'] == '0.0.0.0' and \ + dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError('Tunnel parameters ip key must be set!') + verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) -- cgit v1.2.3 From 91892e431349ca0edb5e3e3023e4f340ab9b777f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 11:58:46 +0200 Subject: ethernet: T3514: bail out early on invalid adapter speed/duplex setting Ethernet adapters have a discrete set of available speed and duplex settings. Instead of passing every value down to ethtool and let it decide, we can do this early in the VyOS verify() function for ethernet interfaces. --- src/conf_mode/interfaces-ethernet.py | 50 +++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 21 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 78c24952b..27c4a7c38 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -73,32 +73,20 @@ def verify(ethernet): ifname = ethernet['ifname'] verify_interface_exists(ifname) + ethtool = Ethtool(ifname) # No need to check speed and duplex keys as both have default values. if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): raise ConfigError('Speed/Duplex missmatch. Must be both auto or manually configured') - verify_mtu(ethernet) - verify_mtu_ipv6(ethernet) - verify_dhcpv6(ethernet) - verify_address(ethernet) - verify_vrf(ethernet) - verify_eapol(ethernet) - verify_mirror(ethernet) + if ethernet['speed'] != 'auto' and ethernet['duplex'] != 'auto': + # We need to verify if the requested speed and duplex setting is + # supported by the underlaying NIC. + speed = ethernet['speed'] + duplex = ethernet['duplex'] + if not ethtool.check_speed_duplex(speed, duplex): + raise ConfigError(f'Adapter does not support speed "{speed}" and duplex "{duplex}"!') - # verify offloading capabilities - if dict_search('offload.rps', ethernet) != None: - if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): - raise ConfigError('Interface does not suport RPS!') - - driver = EthernetIf(ifname).get_driver_name() - # T3342 - Xen driver requires special treatment - if driver == 'vif': - if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: - raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ - 'for MTU size larger then 1500 bytes') - - ethtool = Ethtool(ifname) if 'ring_buffer' in ethernet: max_rx = ethtool.get_rx_buffer() if not max_rx: @@ -118,6 +106,26 @@ def verify(ethernet): raise ConfigError(f'Driver only supports a maximum TX ring-buffer '\ f'size of "{max_tx}" bytes!') + verify_mtu(ethernet) + verify_mtu_ipv6(ethernet) + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) + verify_eapol(ethernet) + verify_mirror(ethernet) + + # verify offloading capabilities + if dict_search('offload.rps', ethernet) != None: + if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): + raise ConfigError('Interface does not suport RPS!') + + driver = EthernetIf(ifname).get_driver_name() + # T3342 - Xen driver requires special treatment + if driver == 'vif': + if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: + raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ + 'for MTU size larger then 1500 bytes') + # XDP requires multiple TX queues if 'xdp' in ethernet: queues = glob(f'/sys/class/net/{ifname}/queues/tx-*') @@ -136,7 +144,7 @@ def generate(ethernet): if 'eapol' in ethernet: render(wpa_suppl_conf.format(**ethernet), 'ethernet/wpa_supplicant.conf.tmpl', ethernet) - + ifname = ethernet['ifname'] cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem') cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key') -- cgit v1.2.3 From cc742d48579e4f76e5d3230d87e22f71f76f9301 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 18:15:47 +0200 Subject: ethernet: T2241: check if interface supports changing speed/duplex settings Not all interface drivers have the ability to change the speed and duplex settings. Known drivers with this limitation are vmxnet3, virtio_net and xen_netfront. If this driver is detected, an error will be presented to the user. --- python/vyos/ethtool.py | 15 +++++++++++++++ src/conf_mode/interfaces-ethernet.py | 3 ++- src/migration-scripts/interfaces/20-to-21 | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 7dcb68346..968e2e2a3 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -13,7 +13,9 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import os import re + from vyos.util import popen class Ethtool: @@ -41,8 +43,18 @@ class Ethtool: # } _speed_duplex = { } _ring_buffers = { } + _driver_name = None def __init__(self, ifname): + # Get driver used for interface + sysfs_file = f'/sys/class/net/{ifname}/device/driver/module' + if os.path.exists(sysfs_file): + link = os.readlink(sysfs_file) + self._driver_name = os.path.basename(link) + + if not self._driver_name: + raise ValueError(f'Could not determine driver for interface {ifname}!') + # Build a dictinary of supported link-speed and dupley settings. out, err = popen(f'ethtool {ifname}') reading = False @@ -142,6 +154,9 @@ class Ethtool: if duplex not in ['full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') + if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + return False + if speed in self._speed_duplex: if duplex in self._speed_duplex[speed]: return True diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 27c4a7c38..889f4856f 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -85,7 +85,8 @@ def verify(ethernet): speed = ethernet['speed'] duplex = ethernet['duplex'] if not ethtool.check_speed_duplex(speed, duplex): - raise ConfigError(f'Adapter does not support speed "{speed}" and duplex "{duplex}"!') + raise ConfigError(f'Adapter does not support changing speed and duplex '\ + f'settings to: {speed}/{duplex}!') if 'ring_buffer' in ethernet: max_rx = ethtool.get_rx_buffer() diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index 4b0e70d35..bd89dcdb4 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -89,6 +89,21 @@ for ifname in config.list_nodes(base): if config.exists(base + [ifname, 'offload', 'ufo']): config.delete(base + [ifname, 'offload', 'ufo']) + # Also while processing the interface configuration, not all adapters support + # changing the speed and duplex settings. If the desired speed and duplex + # values do not work for the NIC driver, we change them back to the default + # value of "auto" - which will be applied if the CLI node is deleted. + speed_path = base + [ifname, 'speed'] + duplex_path = base + [ifname, 'duplex'] + # speed and duplex must always be set at the same time if not set to "auto" + if config.exists(speed_path) and config.exists(duplex_path): + speed = config.return_value(speed_path) + duplex = config.return_value(duplex_path) + if speed != 'auto' and duplex != 'auto': + if not eth.check_speed_duplex(speed, duplex): + config.delete(speed_path) + config.delete(duplex_path) + try: with open(file_name, 'w') as f: f.write(config.to_string()) -- cgit v1.2.3 From 29082959e0efc02462fba8560d6726096e8743e9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 21:50:05 +0200 Subject: ethernet: T3163: only change ring-buffer settings if required Only update the RX/TX ring-buffer settings if they are different from the ones currently programmed to the hardware. There is no need to write the same value to the hardware again - this could cause traffic disruption on some NICs. --- python/vyos/ethtool.py | 29 ++++++++++++++++++++++------- python/vyos/ifconfig/ethernet.py | 15 ++++++++++----- src/conf_mode/interfaces-ethernet.py | 4 ++-- 3 files changed, 34 insertions(+), 14 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index e803e28a1..f5796358d 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -43,6 +43,7 @@ class Ethtool: # } _speed_duplex = { } _ring_buffers = { } + _ring_buffers_max = { } _driver_name = None _auto_negotiation = None @@ -99,10 +100,20 @@ class Ethtool: 'fixed' : fixed } - out, err = popen(f'ethtool -g {ifname}') + out, err = popen(f'ethtool --show-ring {ifname}') # We are only interested in line 2-5 which contains the device maximum # ringbuffers for line in out.splitlines()[2:6]: + if ':' in line: + key, value = [s.strip() for s in line.strip().split(":", 1)] + key = key.lower().replace(' ', '_') + # T3645: ethtool version used on Debian Bullseye changed the + # output format from 0 -> n/a. As we are only interested in the + # tx/rx keys we do not care about RX Mini/Jumbo. + if value.isdigit(): + self._ring_buffers_max[key] = int(value) + # Now we wan't to get the current RX/TX ringbuffer values - used for + for line in out.splitlines()[7:11]: if ':' in line: key, value = [s.strip() for s in line.strip().split(":", 1)] key = key.lower().replace(' ', '_') @@ -143,15 +154,19 @@ class Ethtool: def get_tcp_segmentation_offload(self): return self._get_generic('tcp-segmentation-offload') - def get_rx_buffer(self): - # Configuration of RX ring-buffers is not supported on every device, + def get_ring_buffer_max(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None - return self._ring_buffers.get('rx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return self._ring_buffers_max.get(rx_tx, None) - def get_tx_buffer(self): - # Configuration of TX ring-buffers is not supported on every device, + def get_ring_buffer(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None - return self._ring_buffers.get('tx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return self._ring_buffers.get(rx_tx, None) def check_speed_duplex(self, speed, duplex): """ Check if the passed speed and duplex combination is supported by diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index d4fa3f655..e5da3ac25 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -316,21 +316,26 @@ class EthernetIf(Interface): print('Adapter does not support changing tcp-segmentation-offload settings!') return False - def set_ring_buffer(self, b_type, b_size): + def set_ring_buffer(self, rx_tx, size): """ Example: >>> from vyos.ifconfig import EthernetIf >>> i = EthernetIf('eth0') >>> i.set_ring_buffer('rx', '4096') """ + current_size = self.ethtool.get_ring_buffer(rx_tx) + if current_size == size: + # bail out early if nothing is about to change + return None + ifname = self.config['ifname'] - cmd = f'ethtool -G {ifname} {b_type} {b_size}' + cmd = f'ethtool --set-ring {ifname} {rx_tx} {size}' output, code = self._popen(cmd) # ethtool error codes: # 80 - value already setted # 81 - does not possible to set value if code and code != 80: - print(f'could not set "{b_type}" ring-buffer for {ifname}') + print(f'could not set "{rx_tx}" ring-buffer for {ifname}') return output def update(self, config): @@ -369,8 +374,8 @@ class EthernetIf(Interface): # Set interface ring buffer if 'ring_buffer' in config: - for b_type in config['ring_buffer']: - self.set_ring_buffer(b_type, config['ring_buffer'][b_type]) + for rx_tx, size in config['ring_buffer'].items(): + self.set_ring_buffer(rx_tx, size) # call base class first super().update(config) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 889f4856f..f604f787c 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -89,11 +89,11 @@ def verify(ethernet): f'settings to: {speed}/{duplex}!') if 'ring_buffer' in ethernet: - max_rx = ethtool.get_rx_buffer() + max_rx = ethtool.get_ring_buffer_max('rx') if not max_rx: raise ConfigError('Driver does not support RX ring-buffer configuration!') - max_tx = ethtool.get_tx_buffer() + max_tx = ethtool.get_ring_buffer_max('tx') if not max_tx: raise ConfigError('Driver does not support TX ring-buffer configuration!') -- cgit v1.2.3 From 0229645c8248decb5664056df8aa5cd5dff41802 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 23:03:01 +0200 Subject: vyos.ethtool: T3163: purify code to read and change flow-control settings It makes no sense to have a parser for the ethtool values in ethtool.py and ethernet.py - one instance ios more then enough! --- python/vyos/ethtool.py | 23 +++++++++++++++++ python/vyos/ifconfig/ethernet.py | 42 ++++++++----------------------- src/conf_mode/interfaces-ethernet.py | 4 +++ src/migration-scripts/interfaces/20-to-21 | 8 ++++++ 4 files changed, 45 insertions(+), 32 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index f5796358d..87b9d7dd0 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -46,6 +46,8 @@ class Ethtool: _ring_buffers_max = { } _driver_name = None _auto_negotiation = None + _flow_control = None + _flow_control_enabled = None def __init__(self, ifname): # Get driver used for interface @@ -123,6 +125,15 @@ class Ethtool: if value.isdigit(): self._ring_buffers[key] = int(value) + # Get current flow control settings, but this is not supported by + # all NICs (e.g. vmxnet3 does not support is) + out, err = popen(f'ethtool --show-pause {ifname}') + if len(out.splitlines()) > 1: + self._flow_control = True + # read current flow control setting, this returns: + # ['Autonegotiate:', 'on'] + self._flow_control_enabled = out.splitlines()[1].split()[-1] + def _get_generic(self, feature): """ Generic method to read self._features and return a tuple for feature @@ -186,5 +197,17 @@ class Ethtool: return True return False + def check_flow_control(self): + """ Check if the NIC supports flow-control """ + if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + return False + return self._flow_control + + def get_flow_control(self): + if self._flow_control_enabled == None: + raise ValueError('Interface does not support changing '\ + 'flow-control settings!') + return self._flow_control_enabled + def get_auto_negotiation(self): return self._auto_negotiation diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index e5da3ac25..7bd269491 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -121,38 +121,16 @@ class EthernetIf(Interface): 'flow control settings!') return - # Get current flow control settings: - cmd = f'ethtool --show-pause {ifname}' - output, code = self._popen(cmd) - if code == 76: - # the interface does not support it - return '' - if code: - # never fail here as it prevent vyos to boot - print(f'unexpected return code {code} from {cmd}') - return '' - - # The above command returns - with tabs: - # - # Pause parameters for eth0: - # Autonegotiate: on - # RX: off - # TX: off - if re.search("Autonegotiate:\ton", output): - if enable == "on": - # flowcontrol is already enabled - no need to re-enable it again - # this will prevent the interface from flapping as applying the - # flow-control settings will take the interface down and bring - # it back up every time. - return '' - - # Assemble command executed on system. Unfortunately there is no way - # to change this setting via sysfs - cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' - output, code = self._popen(cmd) - if code: - print(f'could not set flowcontrol for {ifname}') - return output + current = self.ethtool.get_flow_control() + if current != enable: + # Assemble command executed on system. Unfortunately there is no way + # to change this setting via sysfs + cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' + output, code = self._popen(cmd) + if code: + print(f'Could not set flowcontrol for {ifname}') + return output + return None def set_speed_duplex(self, speed, duplex): """ diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index f604f787c..81ed36bf2 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -88,6 +88,10 @@ def verify(ethernet): raise ConfigError(f'Adapter does not support changing speed and duplex '\ f'settings to: {speed}/{duplex}!') + if 'disable_flow_control' in ethernet: + if not ethtool.check_flow_control(): + raise ConfigError('Adapter does not support changing flow-control settings!') + if 'ring_buffer' in ethernet: max_rx = ethtool.get_ring_buffer_max('rx') if not max_rx: diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index bd89dcdb4..0bd858760 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -104,6 +104,14 @@ for ifname in config.list_nodes(base): config.delete(speed_path) config.delete(duplex_path) + # Also while processing the interface configuration, not all adapters support + # changing disabling flow-control - or change this setting. If disabling + # flow-control is not supported by the NIC, we remove the setting from CLI + flow_control_path = base + [ifname, 'disable-flow-control'] + if config.exists(flow_control_path): + if not eth.check_flow_control(): + config.delete(flow_control_path) + try: with open(file_name, 'w') as f: f.write(config.to_string()) -- cgit v1.2.3 From 468ba7b076c7145b7fe62b60b7e81b432bb27d54 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 31 Aug 2021 10:48:12 +0000 Subject: tunnel: T2920: Add checks tun with same source addr and keys 2 tunnels with the same local-address should has different keys Check existing tunnels (source-address key) with new tunnel. --- src/conf_mode/interfaces-tunnel.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 616a2e23c..bfd9a8c56 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -18,6 +18,7 @@ import os from sys import exit from netifaces import interfaces +from ipaddress import IPv4Address from vyos.config import Config from vyos.configdict import dict_merge @@ -31,6 +32,7 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface +from vyos.ifconfig import Section from vyos.ifconfig import TunnelIf from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -100,6 +102,27 @@ def verify(tunnel): dict_search('parameters.ip.key', tunnel) == None: raise ConfigError('Tunnel parameters ip key must be set!') + if tunnel['encapsulation'] in ['gre', 'gretap']: + if dict_search('parameters.ip.key', tunnel) != None: + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for tunnel_if in Section.interfaces('tunnel'): + tunnel_cfg = get_interface_config(tunnel_if) + exist_encap = tunnel_cfg['linkinfo']['info_kind'] + exist_source_address = tunnel_cfg['address'] + exist_key = tunnel_cfg['linkinfo']['info_data']['ikey'] + new_source_address = tunnel['source_address'] + # Convert tunnel key to ip key, format "ip -j link show" + # 1 => 0.0.0.1, 999 => 0.0.3.231 + orig_new_key = int(tunnel['parameters']['ip']['key']) + new_key = IPv4Address(orig_new_key) + new_key = str(new_key) + if tunnel['encapsulation'] == exist_encap and \ + new_source_address == exist_source_address and \ + new_key == exist_key: + raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ + f'is already used for tunnel "{tunnel_if}"!') + verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) -- cgit v1.2.3 From 8032f4bb72db064d2b3f6f0954f6674091822cc4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 2 Sep 2021 16:05:40 +0200 Subject: login: radius: T3192: drop workaround required by get_config_dict() The workaround is no longer required, as the issue was resolved in get_config_dict() so if it is a node, a list is always returned. --- src/conf_mode/system-login.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index f0b92aea8..93696e653 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -80,12 +80,6 @@ def get_config(config=None): login['radius']['server'][server] = dict_merge(default_values, login['radius']['server'][server]) - # XXX: for a yet unknown reason when we only have one source-address - # get_config_dict() will show a string over a string - if 'radius' in login and 'source_address' in login['radius']: - if isinstance(login['radius']['source_address'], str): - login['radius']['source_address'] = [login['radius']['source_address']] - # create a list of all users, cli and users all_users = list(set(local_users + cli_users)) # We will remove any normal users that dos not exist in the current -- cgit v1.2.3 From 658de9ea0fbe91e593f9cf0a8c434791282af100 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 2 Sep 2021 16:08:57 +0200 Subject: login: T3792: bugfix for usernames containing a hyphen While migrating to get_config_dict() in commit e8a1c291b1 ("login: radius: T3192: migrate to get_config_dict()") the user-name was not excluded from mangling (no_tag_node_value_mangle=True). This resulted in a username "vyos-user" from CLI to be actually created as "vyos_user" on the system. This commit also adds respective Smoketests to prevent this in the future. --- smoketest/scripts/cli/test_system_login.py | 41 +++++++++++++++++++++++++++--- src/conf_mode/system-login.py | 2 +- 2 files changed, 39 insertions(+), 4 deletions(-) (limited to 'src/conf_mode') diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 8327235fb..af3a5851c 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -31,7 +31,19 @@ from vyos.util import read_file from vyos.template import inc_ip base_path = ['system', 'login'] -users = ['vyos1', 'vyos2'] +users = ['vyos1', 'vyos-roxx123', 'VyOS-123_super.Nice'] + +ssh_pubkey = """ +AAAAB3NzaC1yc2EAAAADAQABAAABgQD0NuhUOEtMIKnUVFIHoFatqX/c4mjerXyF +TlXYfVt6Ls2NZZsUSwHbnhK4BKDrPvVZMW/LycjQPzWW6TGtk6UbZP1WqdviQ9hP +jsEeKJSTKciMSvQpjBWyEQQPXSKYQC7ryQQilZDqnJgzqwzejKEe+nhhOdBvjuZc +uukxjT69E0UmWAwLxzvfiurwiQaC7tG+PwqvtfHOPL3i6yRO2C5ORpFarx8PeGDS +IfIXJCr3LoUbLHeuE7T2KaOKQcX0UsWJ4CoCapRLpTVYPDB32BYfgq7cW1Sal1re +EGH2PzuXBklinTBgCHA87lHjpwDIAqdmvMj7SXIW9LxazLtP+e37sexE7xEs0cpN +l68txdDbY2P2Kbz5mqGFfCvBYKv9V2clM5vyWNy/Xp5TsCis89nn83KJmgFS7sMx +pHJz8umqkxy3hfw0K7BRFtjWd63sbOP8Q/SDV7LPaIfIxenA9zv2rY7y+AIqTmSr +TTSb0X1zPGxPIRFy5GoGtO9Mm5h4OZk= +""" class TestSystemLogin(VyOSUnitTestSHIM.TestCase): def tearDown(self): @@ -42,6 +54,8 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_add_linux_system_user(self): + # We are not allowed to re-use a username already taken by the Linux + # base system system_user = 'backup' self.cli_set(base_path + ['user', system_user, 'authentication', 'plaintext-password', system_user]) @@ -75,9 +89,30 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): (stdout, stderr) = proc.communicate() # stdout is something like this: - # b'Linux vyos 4.19.101-amd64-vyos #1 SMP Sun Feb 2 10:18:07 UTC 2020 x86_64 GNU/Linux\n' + # b'Linux LR1.wue3 5.10.61-amd64-vyos #1 SMP Fri Aug 27 08:55:46 UTC 2021 x86_64 GNU/Linux\n' self.assertTrue(len(stdout) > 40) + def test_system_user_ssh_key(self): + ssh_user = 'ssh-test_user' + public_keys = 'vyos' + type = 'ssh-rsa' + + self.cli_set(base_path + ['user', ssh_user, 'authentication', 'public-keys', public_keys, 'key', ssh_pubkey.replace('\n','')]) + + # check validate() - missing type for public-key + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['user', ssh_user, 'authentication', 'public-keys', public_keys, 'type', type]) + + self.cli_commit() + + # Check that SSH key was written properly + tmp = cmd(f'sudo cat /home/{ssh_user}/.ssh/authorized_keys') + key = f'{type} ' + ssh_pubkey.replace('\n','') + self.assertIn(key, tmp) + + self.cli_delete(base_path + ['user', ssh_user]) + def test_radius_kernel_features(self): # T2886: RADIUS requires some Kernel options to be present kernel = platform.release() @@ -201,4 +236,4 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): self.assertTrue(tmp) if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 93696e653..318ff276d 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -59,7 +59,7 @@ def get_config(config=None): conf = Config() base = ['system', 'login'] login = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) + no_tag_node_value_mangle=True, get_first_key=True) # users no longer existing in the running configuration need to be deleted local_users = get_local_users() -- cgit v1.2.3 From 7e84566dedfdc532ffe05b404005daa6f21df567 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 2 Sep 2021 18:58:11 +0000 Subject: tunnel: T3788: Add check keys for ipip and sit Keys are not allowed with ipip and sit tunnels --- src/conf_mode/interfaces-tunnel.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index bfd9a8c56..ef385d2e7 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -123,6 +123,11 @@ def verify(tunnel): raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ f'is already used for tunnel "{tunnel_if}"!') + # Keys are not allowed with ipip and sit tunnels + if tunnel['encapsulation'] in ['ipip', 'sit']: + if dict_search('parameters.ip.key', tunnel) != None: + raise ConfigError('Keys are not allowed with ipip and sit tunnels!') + verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) -- cgit v1.2.3 From 5f1c1ae4770fe36b5290f34d2f3a248c6b1a0ddb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 3 Sep 2021 20:44:42 +0200 Subject: bgp: T3798: add support for neighbor local-as replace-as --- data/templates/frr/bgpd.frr.tmpl | 5 +++-- interface-definitions/include/bgp/neighbor-local-as.xml.i | 12 +++++++++--- smoketest/scripts/cli/test_protocols_bgp.py | 8 +++++--- src/conf_mode/protocols_bgp.py | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 96815836b..4ac2127cb 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -46,8 +46,9 @@ neighbor {{ neighbor }} {{ graceful_restart }} {% endif %} {% if config.local_as is defined and config.local_as is not none %} -{% for local_asn in config.local_as %} - neighbor {{ neighbor }} local-as {{ local_asn }} {{ 'no-prepend' if config.local_as[local_asn].no_prepend is defined }} +{% for local_asn, local_asn_config in config.local_as.items() %} +{# There can be only one local-as value, this is checked in the Python code #} + neighbor {{ neighbor }} local-as {{ local_asn }} {{ 'no-prepend' if local_asn_config.no_prepend is defined }} {{ 'replace-as' if local_asn_config.replace_as is defined }} {% endfor %} {% endif %} {% if config.override_capability is defined %} diff --git a/interface-definitions/include/bgp/neighbor-local-as.xml.i b/interface-definitions/include/bgp/neighbor-local-as.xml.i index 28c6b72b6..8cf0167fd 100644 --- a/interface-definitions/include/bgp/neighbor-local-as.xml.i +++ b/interface-definitions/include/bgp/neighbor-local-as.xml.i @@ -1,10 +1,10 @@ - Local AS number [REQUIRED] + Specify alternate ASN for this BGP process u32:1-4294967294 - Local AS number + Autonomous System Number (ASN) @@ -13,7 +13,13 @@ - Disable prepending local-as to updates from EBGP peers + Disable prepending local-as from/to updates for eBGP peers + + + + + + Prepend only local-as from/to updates for eBGP peers diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index df9dc342b..05919abbc 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -165,7 +165,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in peer_config: self.assertIn(f' neighbor {peer} ebgp-multihop {peer_config["multi_hop"]}', frrconfig) if 'local_as' in peer_config: - self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]}', frrconfig) + self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]} no-prepend replace-as', frrconfig) if 'cap_over' in peer_config: self.assertIn(f' neighbor {peer} override-capability', frrconfig) if 'passive' in peer_config: @@ -284,7 +284,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]]) if 'local_as' in peer_config: - self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"]]) + self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend']) + self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'replace-as']) if 'cap_over' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'override-capability']) if 'passive' in peer_config: @@ -353,7 +354,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in config: self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) if 'local_as' in config: - self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend']) + self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'replace-as']) if 'cap_over' in config: self.cli_set(base_path + ['peer-group', peer_group, 'override-capability']) if 'passive' in config: diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 7d05eed9f..e24fcef14 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -130,7 +130,7 @@ def verify(bgp): if 'local_as' in peer_config: if len(peer_config['local_as']) > 1: - raise ConfigError('Only one local-as number may be specified!') + raise ConfigError(f'Only one local-as number can be specified for peer "{peer}"!') # Neighbor local-as override can not be the same as the local-as # we use for this BGP instane! -- cgit v1.2.3 From 6b52387190f8213e7e02060e894c6ddd4fb7cb3d Mon Sep 17 00:00:00 2001 From: Paul Lettington Date: Fri, 3 Sep 2021 23:39:22 +0100 Subject: login: T971 allow quoting in public-keys options This patch allows the use of `"` in ssh public-key options which unlocks the ability to set the `from` option in a way that sshd will accept to limit what hosts a user can connect from. --- src/conf_mode/system-login.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 318ff276d..4dd7f936d 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -240,7 +240,9 @@ def apply(login): # XXX: Should we deny using root at all? home_dir = getpwnam(user).pw_dir render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.tmpl', - user_config, permission=0o600, user=user, group='users') + user_config, permission=0o600, + formater=lambda _: _.replace(""", '"'), + user=user, group='users') except Exception as e: raise ConfigError(f'Adding user "{user}" raised exception: "{e}"') -- cgit v1.2.3 From dd210c92beeffae07f26dc72ab51d9a93219b582 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Sep 2021 10:18:19 +0200 Subject: bgp: T3798: "replace-as" option can only be used when "no-prepend" is defined Commit 5f1c1ae4 ("bgp: T3798: add support for neighbor local-as replace-as") added support for a new CLI option when the local-as is changed for a specified neighbor or peer-group. There was an error in the CLI / design as the "replace-as" option can only be used when "no-prepend" is defined. Thus "no-prepend" became a and the new "replace-as" leafNode is now a child of "no-prepend". --- data/templates/frr/bgpd.frr.tmpl | 4 ++-- .../include/bgp/neighbor-local-as.xml.i | 19 ++++++++++--------- smoketest/scripts/cli/test_protocols_bgp.py | 6 ++---- src/conf_mode/protocols_bgp.py | 4 ++-- 4 files changed, 16 insertions(+), 17 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 4ac2127cb..987b922da 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -46,9 +46,9 @@ neighbor {{ neighbor }} {{ graceful_restart }} {% endif %} {% if config.local_as is defined and config.local_as is not none %} -{% for local_asn, local_asn_config in config.local_as.items() %} +{% for local_as, local_as_config in config.local_as.items() %} {# There can be only one local-as value, this is checked in the Python code #} - neighbor {{ neighbor }} local-as {{ local_asn }} {{ 'no-prepend' if local_asn_config.no_prepend is defined }} {{ 'replace-as' if local_asn_config.replace_as is defined }} + neighbor {{ neighbor }} local-as {{ local_as }} {{ 'no-prepend' if local_as_config.no_prepend is defined }} {{ 'replace-as' if local_as_config.no_prepend is defined and local_as_config.no_prepend.replace_as is defined }} {% endfor %} {% endif %} {% if config.override_capability is defined %} diff --git a/interface-definitions/include/bgp/neighbor-local-as.xml.i b/interface-definitions/include/bgp/neighbor-local-as.xml.i index 8cf0167fd..8868e3093 100644 --- a/interface-definitions/include/bgp/neighbor-local-as.xml.i +++ b/interface-definitions/include/bgp/neighbor-local-as.xml.i @@ -11,18 +11,19 @@ - + Disable prepending local-as from/to updates for eBGP peers - - - - - Prepend only local-as from/to updates for eBGP peers - - - + + + + Prepend only local-as from/to updates for eBGP peers + + + + + diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 05919abbc..29b5aa9d1 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -284,8 +284,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]]) if 'local_as' in peer_config: - self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend']) - self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'replace-as']) + self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend', 'replace-as']) if 'cap_over' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'override-capability']) if 'passive' in peer_config: @@ -354,8 +353,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'multi_hop' in config: self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) if 'local_as' in config: - self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend']) - self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'replace-as']) + self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend', 'replace-as']) if 'cap_over' in config: self.cli_set(base_path + ['peer-group', peer_group, 'override-capability']) if 'passive' in config: diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index e24fcef14..68284e0f9 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -140,7 +140,7 @@ def verify(bgp): # ttl-security and ebgp-multihop can't be used in the same configration if 'ebgp_multihop' in peer_config and 'ttl_security' in peer_config: - raise ConfigError('You can\'t set both ebgp-multihop and ttl-security hops') + raise ConfigError('You can not set both ebgp-multihop and ttl-security hops') # Check if neighbor has both override capability and strict capability match configured at the same time. if 'override_capability' in peer_config and 'strict_capability_match' in peer_config: @@ -148,7 +148,7 @@ def verify(bgp): # Check spaces in the password if 'password' in peer_config and ' ' in peer_config['password']: - raise ConfigError('You can\'t use spaces in the password') + raise ConfigError('Whitespace is not allowed in passwords!') # Some checks can/must only be done on a neighbor and not a peer-group if neighbor == 'neighbor': -- cgit v1.2.3 From f7b1017559f4a511b116a5acd4705a15caf97865 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 Sep 2021 17:56:28 +0200 Subject: name-server: T3804: merge "system name-servers-dhcp" into "system name-server" We have "set system name-server " to specify a name-server IP address we wan't to use. We also have "set system name-servers-dhcp " which does the same, but the name-server in question is retrieved via DHCP. Both CLI nodes are combined under "set system name-server " to keep things as they are in real life - we need a name-server. (cherry picked from commit 2ecf7a9f9cbe9359457bd23b4a0c45f3763123c7) --- interface-definitions/dns-domain-name.xml.in | 25 +++++++------- src/conf_mode/host_name.py | 49 ++++++++++++++++------------ src/migration-scripts/system/20-to-21 | 48 +++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 34 deletions(-) create mode 100755 src/migration-scripts/system/20-to-21 (limited to 'src/conf_mode') diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index ff632e1d1..2b1644609 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -1,37 +1,34 @@ - - Domain Name Servers (DNS) used by the system (resolv.conf) + System Domain Name Servers (DNS) 400 + + + ipv4 - Domain Name Server (DNS) address + Domain Name Server IPv4 address ipv6 - Domain Name Server (DNS) address + Domain Name Server IPv6 address + + + txt + Use Domain Name Server from DHCP interface + - - - Interfaces whose DHCP client nameservers will be used by the system (resolv.conf) - 400 - - - - - - System host name (default: vyos) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index f4c75c257..a7135911d 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2021 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 @@ -14,10 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -conf-mode script for 'system host-name' and 'system domain-name'. -""" - import re import sys import copy @@ -25,10 +21,13 @@ import copy import vyos.util import vyos.hostsd_client -from vyos.config import Config from vyos import ConfigError -from vyos.util import cmd, call, process_named_running - +from vyos.config import Config +from vyos.ifconfig import Section +from vyos.template import is_ip +from vyos.util import cmd +from vyos.util import call +from vyos.util import process_named_running from vyos import airbag airbag.enable() @@ -37,7 +36,7 @@ default_config_data = { 'domain_name': '', 'domain_search': [], 'nameserver': [], - 'nameservers_dhcp_interfaces': [], + 'nameservers_dhcp_interfaces': {}, 'static_host_mapping': {} } @@ -51,29 +50,37 @@ def get_config(config=None): hosts = copy.deepcopy(default_config_data) - hosts['hostname'] = conf.return_value("system host-name") + hosts['hostname'] = conf.return_value(['system', 'host-name']) # This may happen if the config is not loaded yet, # e.g. if run by cloud-init if not hosts['hostname']: hosts['hostname'] = default_config_data['hostname'] - if conf.exists("system domain-name"): - hosts['domain_name'] = conf.return_value("system domain-name") + if conf.exists(['system', 'domain-name']): + hosts['domain_name'] = conf.return_value(['system', 'domain-name']) hosts['domain_search'].append(hosts['domain_name']) - for search in conf.return_values("system domain-search domain"): + for search in conf.return_values(['system', 'domain-search', 'domain']): hosts['domain_search'].append(search) - hosts['nameserver'] = conf.return_values("system name-server") + if conf.exists(['system', 'name-server']): + for ns in conf.return_values(['system', 'name-server']): + if is_ip(ns): + hosts['nameserver'].append(ns) + else: + tmp = '' + if_type = Section.section(ns) + if conf.exists(['interfaces', if_type, ns, 'address']): + tmp = conf.return_values(['interfaces', if_type, ns, 'address']) - hosts['nameservers_dhcp_interfaces'] = conf.return_values("system name-servers-dhcp") + hosts['nameservers_dhcp_interfaces'].update({ ns : tmp }) # system static-host-mapping - for hn in conf.list_nodes('system static-host-mapping host-name'): + for hn in conf.list_nodes(['system', 'static-host-mapping', 'host-name']): hosts['static_host_mapping'][hn] = {} - hosts['static_host_mapping'][hn]['address'] = conf.return_value(f'system static-host-mapping host-name {hn} inet') - hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(f'system static-host-mapping host-name {hn} alias') + hosts['static_host_mapping'][hn]['address'] = conf.return_value(['system', 'static-host-mapping', 'host-name', hn, 'inet']) + hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(['system', 'static-host-mapping', 'host-name', hn, 'alias']) return hosts @@ -103,8 +110,10 @@ def verify(hosts): if not hostname_regex.match(a) and len(a) != 0: raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"') - # TODO: add warnings for nameservers_dhcp_interfaces if interface doesn't - # exist or doesn't have address dhcp(v6) + for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items(): + # Warnin user if interface does not have DHCP or DHCPv6 configured + if not set(interface_config).intersection(['dhcp', 'dhcpv6']): + print(f'WARNING: "{interface}" is not a DHCP interface but uses DHCP name-server option!') return None diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21 new file mode 100755 index 000000000..1728995de --- /dev/null +++ b/src/migration-scripts/system/20-to-21 @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +# T3795: merge "system name-servers-dhcp" into "system name-server" + +import os + +from sys import argv +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['system', 'name-servers-dhcp'] +config = ConfigTree(config_file) +if not config.exists(base): + # Nothing to do + exit(0) + +for interface in config.return_values(base): + config.set(['system', 'name-server'], value=interface, replace=False) + +config.delete(base) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From 1fc69810450a306310fc6bf82cd0b2406d029fb2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Sep 2021 12:12:40 +0200 Subject: pki: T3642: verify() that we can not delete certificates still referenced in CLI --- src/conf_mode/pki.py | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index ef1b57650..efa3578b4 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -16,8 +16,11 @@ from sys import exit +import jmespath + from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import node_changed from vyos.pki import is_ca_certificate from vyos.pki import load_certificate from vyos.pki import load_certificate_request @@ -26,6 +29,7 @@ from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters from vyos.util import ask_input +from vyos.util import dict_search_recursive from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -37,14 +41,29 @@ def get_config(config=None): else: conf = Config() base = ['pki'] - if not conf.exists(base): - return None pki = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) + + pki['changed'] = {} + tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_')) + if tmp: pki['changed'].update({'ca' : tmp}) + + tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_')) + if tmp: pki['changed'].update({'certificate' : tmp}) + + # We only merge on the defaults of there is a configuration at all + if conf.exists(base): + default_values = defaults(base) + pki = dict_merge(default_values, pki) + + # We need to get the entire system configuration to verify that we are not + # deleting a certificate that is still referenced somewhere! + pki['system'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - default_values = defaults(base) - pki = dict_merge(default_values, pki) return pki def is_valid_certificate(raw_data): @@ -142,6 +161,21 @@ def verify(pki): if len(country) != 2 or not country.isalpha(): raise ConfigError(f'Invalid default country value. Value must be 2 alpha characters.') + if 'changed' in pki: + # if the list is getting longer, we can move to a dict() and also embed the + # search key as value from line 173 or 176 + for cert_type in ['ca', 'certificate']: + if not cert_type in pki['changed']: + continue + for certificate in pki['changed'][cert_type]: + if cert_type not in pki or certificate not in pki['changed'][cert_type]: + if cert_type == 'ca': + if certificate in dict_search_recursive(pki['system'], 'ca_certificate'): + raise ConfigError(f'CA certificate "{certificate}" is still in use!') + elif cert_type == 'certificate': + if certificate in dict_search_recursive(pki['system'], 'certificate'): + raise ConfigError(f'Certificate "{certificate}" is still in use!') + return None def generate(pki): @@ -154,6 +188,8 @@ def apply(pki): if not pki: return None + # XXX: restart services if the content of a certificate changes + return None if __name__ == '__main__': -- cgit v1.2.3 From 84a429b41175b95634ec9492e0cf3a564a47abdd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Sep 2021 21:17:42 +0200 Subject: ifconfig: T3806: "ipv6 address no_default_link_local" required for MTU < 1280 This commit also extends the smoketest to verify that the exception for this error is raised. --- python/vyos/configverify.py | 24 ++++++++++++------------ smoketest/scripts/cli/base_interfaces_test.py | 10 +++++++++- src/conf_mode/interfaces-ethernet.py | 15 +++++++-------- 3 files changed, 28 insertions(+), 21 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 7f49aa9af..760255d0e 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -67,22 +67,22 @@ def verify_mtu_ipv6(config): min_mtu = 1280 if int(config['mtu']) < min_mtu: interface = config['ifname'] - error_msg = f'IPv6 address will be configured on interface "{interface}" ' \ - f'thus the minimum MTU requirement is {min_mtu}!' + error_msg = f'IPv6 address will be configured on interface "{interface}",\n' \ + f'the required minimum MTU is {min_mtu}!' - for address in (dict_search('address', config) or []): - if address in ['dhcpv6'] or is_ipv6(address): - raise ConfigError(error_msg) + if 'address' in config: + for address in config['address']: + if address in ['dhcpv6'] or is_ipv6(address): + raise ConfigError(error_msg) - tmp = dict_search('ipv6.address', config) - if tmp and 'no_default_link_local' not in tmp: - raise ConfigError('link-local ' + error_msg) + tmp = dict_search('ipv6.address.no_default_link_local', config) + if tmp == None: raise ConfigError('link-local ' + error_msg) - if tmp and 'autoconf' in tmp: - raise ConfigError(error_msg) + tmp = dict_search('ipv6.address.autoconf', config) + if tmp != None: raise ConfigError(error_msg) - if tmp and 'eui64' in tmp: - raise ConfigError(error_msg) + tmp = dict_search('ipv6.address.eui64', config) + if tmp != None: raise ConfigError(error_msg) def verify_vrf(config): """ diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index edb604dbf..6a5b9c4ee 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -246,11 +246,19 @@ class BasicInterfaceTest: for intf in self._interfaces: base = self._base_path + [intf] self.cli_set(base + ['mtu', self._mtu]) - self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) for option in self._options.get(intf, []): self.cli_set(base + option.split()) + # check validate() - can not set low MTU if 'no-default-link-local' + # is not set on CLI + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + for intf in self._interfaces: + base = self._base_path + [intf] + self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) + # commit interface changes self.cli_commit() diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 81ed36bf2..2a094af52 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -72,6 +72,13 @@ def verify(ethernet): ifname = ethernet['ifname'] verify_interface_exists(ifname) + verify_mtu(ethernet) + verify_mtu_ipv6(ethernet) + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) + verify_eapol(ethernet) + verify_mirror(ethernet) ethtool = Ethtool(ifname) # No need to check speed and duplex keys as both have default values. @@ -111,14 +118,6 @@ def verify(ethernet): raise ConfigError(f'Driver only supports a maximum TX ring-buffer '\ f'size of "{max_tx}" bytes!') - verify_mtu(ethernet) - verify_mtu_ipv6(ethernet) - verify_dhcpv6(ethernet) - verify_address(ethernet) - verify_vrf(ethernet) - verify_eapol(ethernet) - verify_mirror(ethernet) - # verify offloading capabilities if dict_search('offload.rps', ethernet) != None: if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): -- cgit v1.2.3 From b45cef9185cc78de7bc4170403e47b8ee1d14e3b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Sep 2021 21:22:57 +0200 Subject: pki: eapol: T3642: use write_file() to store certificates --- src/conf_mode/interfaces-ethernet.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 2a094af52..31998a9a8 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -37,6 +37,7 @@ from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call from vyos.util import dict_search +from vyos.util import write_file from vyos import ConfigError from vyos import airbag airbag.enable() @@ -156,19 +157,16 @@ def generate(ethernet): cert_name = ethernet['eapol']['certificate'] pki_cert = ethernet['pki']['certificate'][cert_name] - with open(cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_cert['certificate'])) - - with open(cert_key_path, 'w') as f: - f.write(wrap_private_key(pki_cert['private']['key'])) + write_file(cert_file_path, wrap_certificate(pki_cert['certificate'])) + write_file(cert_key_path, wrap_private_key(pki_cert['private']['key'])) if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') ca_cert_name = ethernet['eapol']['ca_certificate'] pki_ca_cert = ethernet['pki']['ca'][cert_name] - with open(ca_cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_ca_cert['certificate'])) + write_file(ca_cert_file_path, + wrap_certificate(pki_ca_cert['certificate'])) else: # delete configuration on interface removal if os.path.isfile(wpa_suppl_conf.format(**ethernet)): -- cgit v1.2.3 From 3cd59879451504b4da865cc066112b1da882e5af Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Sep 2021 21:23:44 +0200 Subject: pki: eapol: T3642: only add "pki" key to interface dict if pki is configured --- python/vyos/configverify.py | 3 +- smoketest/scripts/cli/test_interfaces_ethernet.py | 42 +++++++++++++++++------ src/conf_mode/interfaces-ethernet.py | 10 +++--- 3 files changed, 38 insertions(+), 17 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 760255d0e..8aca76568 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -152,11 +152,10 @@ def verify_eapol(config): if 'certificate' not in config['eapol']: raise ConfigError('Certificate must be specified when using EAPoL!') - if 'certificate' not in config['pki']: + if 'pki' not in config or 'certificate' not in config['pki']: raise ConfigError('Invalid certificate specified for EAPoL') cert_name = config['eapol']['certificate'] - if cert_name not in config['pki']['certificate']: raise ConfigError('Invalid certificate specified for EAPoL') diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index a9cdab16a..6d80e4c96 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -25,9 +25,26 @@ from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file -pki_path = ['pki'] -cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0=' -key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww' +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0= +""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +""" def get_wpa_supplicant_value(interface, key): tmp = read_file(f'/run/wpa_supplicant/{interface}.conf') @@ -64,10 +81,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): # call base-classes classmethod super(cls, cls).setUpClass() - def tearDown(self): - self.cli_delete(pki_path) - for interface in self._interfaces: # when using a dedicated interface to test via TEST_ETH environment # variable only this one will be cleared in the end - usable to test @@ -151,14 +165,17 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.cli_commit() def test_eapol_support(self): - self.cli_set(pki_path + ['ca', 'eapol', 'certificate', cert_data]) - self.cli_set(pki_path + ['certificate', 'eapol', 'certificate', cert_data]) - self.cli_set(pki_path + ['certificate', 'eapol', 'private', 'key', key_data]) + ca_name = 'eapol' + cert_name = 'eapol' + + self.cli_set(['pki', 'ca', ca_name, 'certificate', cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'certificate', cert_data.replace('\n','')]) + self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', key_data.replace('\n','')]) for interface in self._interfaces: # Enable EAPoL - self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol']) - self.cli_set(self._base_path + [interface, 'eapol', 'certificate', 'eapol']) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', ca_name]) + self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() @@ -189,5 +206,8 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): tmp = get_wpa_supplicant_value(interface, 'identity') self.assertEqual(f'"{mac}"', tmp) + self.cli_delete(['pki', 'ca', ca_name]) + self.cli_delete(['pki', 'certificate', cert_name]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 31998a9a8..21a04f954 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -55,15 +55,17 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['interfaces', 'ethernet'] - tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + # This must be called prior to get_interface_dict(), as this function will + # alter the config level (config.set_level()) + pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + base = ['interfaces', 'ethernet'] ethernet = get_interface_dict(conf, base) if 'deleted' not in ethernet: - ethernet['pki'] = tmp_pki + if pki: ethernet['pki'] = pki return ethernet -- cgit v1.2.3 From 63fbd8c663c8c42ad178d6f0694f20bb98acf01a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 8 Sep 2021 14:33:14 +0200 Subject: openvpn: T3805: use vyos.util.write_file() to store certificates --- python/vyos/util.py | 4 +-- src/conf_mode/interfaces-openvpn.py | 58 ++++++++++--------------------------- 2 files changed, 18 insertions(+), 44 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/util.py b/python/vyos/util.py index b41c5b346..849b27d3b 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -197,7 +197,7 @@ def read_file(fname, defaultonfailure=None): return defaultonfailure raise e -def write_file(fname, data, defaultonfailure=None, user=None, group=None): +def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None): """ Write content of data to given fname, should defaultonfailure be not None, it is returned on failure to read. @@ -215,6 +215,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None): with open(fname, 'w') as f: bytes = f.write(data) chown(fname, user, group) + chmod(fname, mode) return bytes except Exception as e: if defaultonfailure is not None: @@ -295,7 +296,6 @@ def makedir(path, user=None, group=None): os.makedirs(path, mode=0o755) chown(path, user, group) - def colon_separated_to_dict(data_string, uniquekeys=False): """ Converts a string containing newline-separated entries of colon-separated key-value pairs into a dict. diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 6be4e918b..274bc655e 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -45,9 +45,9 @@ from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.util import call from vyos.util import chown -from vyos.util import chmod_600 from vyos.util import dict_search from vyos.util import dict_search_args +from vyos.util import write_file from vyos.validate import is_addr_assigned from vyos import ConfigError @@ -449,7 +449,6 @@ def verify(openvpn): def generate_pki_files(openvpn): pki = openvpn['pki'] - if not pki: return None @@ -457,16 +456,11 @@ def generate_pki_files(openvpn): shared_secret_key = dict_search_args(openvpn, 'shared_secret_key') tls = dict_search_args(openvpn, 'tls') - files = [] - if shared_secret_key: pki_key = pki['openvpn']['shared_secret'][shared_secret_key] key_path = os.path.join(cfg_dir, f'{interface}_shared.key') - - with open(key_path, 'w') as f: - f.write(wrap_openvpn_key(pki_key['key'])) - - files.append(key_path) + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group) if tls: if 'ca_certificate' in tls: @@ -475,20 +469,15 @@ def generate_pki_files(openvpn): if 'certificate' in pki_ca: cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem') - - with open(cert_path, 'w') as f: - f.write(wrap_certificate(pki_ca['certificate'])) - - files.append(cert_path) + write_file(cert_path, wrap_certificate(pki_ca['certificate']), + user=user, group=group, mode=0o600) if 'crl' in pki_ca: for crl in pki_ca['crl']: crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem') + write_file(crl_path, wrap_crl(crl), user=user, group=group, + mode=0o600) - with open(crl_path, 'w') as f: - f.write(wrap_crl(crl)) - - files.append(crl_path) openvpn['tls']['crl'] = True if 'certificate' in tls: @@ -497,19 +486,14 @@ def generate_pki_files(openvpn): if 'certificate' in pki_cert: cert_path = os.path.join(cfg_dir, f'{interface}_cert.pem') - - with open(cert_path, 'w') as f: - f.write(wrap_certificate(pki_cert['certificate'])) - - files.append(cert_path) + write_file(cert_path, wrap_certificate(pki_cert['certificate']), + user=user, group=group, mode=0o600) if 'private' in pki_cert and 'key' in pki_cert['private']: key_path = os.path.join(cfg_dir, f'{interface}_cert.key') + write_file(key_path, wrap_private_key(pki_cert['private']['key']), + user=user, group=group, mode=0o600) - with open(key_path, 'w') as f: - f.write(wrap_private_key(pki_cert['private']['key'])) - - files.append(key_path) openvpn['tls']['private_key'] = True if 'dh_params' in tls: @@ -518,11 +502,8 @@ def generate_pki_files(openvpn): if 'parameters' in pki_dh: dh_path = os.path.join(cfg_dir, f'{interface}_dh.pem') - - with open(dh_path, 'w') as f: - f.write(wrap_dh_parameters(pki_dh['parameters'])) - - files.append(dh_path) + write_file(dh_path, wrap_dh_parameters(pki_dh['parameters']), + user=user, group=group, mode=0o600) if 'auth_key' in tls: key_name = tls['auth_key'] @@ -530,11 +511,8 @@ def generate_pki_files(openvpn): if 'key' in pki_key: key_path = os.path.join(cfg_dir, f'{interface}_auth.key') - - with open(key_path, 'w') as f: - f.write(wrap_openvpn_key(pki_key['key'])) - - files.append(key_path) + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group, mode=0o600) if 'crypt_key' in tls: key_name = tls['crypt_key'] @@ -570,7 +548,7 @@ def generate(openvpn): chown(ccd_dir, user, group) # Fix file permissons for keys - fix_permissions = generate_pki_files(openvpn) + generate_pki_files(openvpn) # Generate User/Password authentication file if 'authentication' in openvpn: @@ -598,10 +576,6 @@ def generate(openvpn): render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn, formater=lambda _: _.replace(""", '"'), user=user, group=group) - # Fixup file permissions - for file in fix_permissions: - chmod_600(file) - return None def apply(openvpn): -- cgit v1.2.3 From 84e912ab2f583864e637c2df137f62f3d4cbeb14 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 8 Sep 2021 14:34:41 +0200 Subject: openvpn: T3805: use vyos.util.makedir() to create system directories --- src/conf_mode/interfaces-openvpn.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 274bc655e..c837328be 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -47,6 +47,7 @@ from vyos.util import call from vyos.util import chown from vyos.util import dict_search from vyos.util import dict_search_args +from vyos.util import makedir from vyos.util import write_file from vyos.validate import is_addr_assigned @@ -520,18 +521,17 @@ def generate_pki_files(openvpn): if 'key' in pki_key: key_path = os.path.join(cfg_dir, f'{interface}_crypt.key') - - with open(key_path, 'w') as f: - f.write(wrap_openvpn_key(pki_key['key'])) - - files.append(key_path) - - return files + write_file(key_path, wrap_openvpn_key(pki_key['key']), + user=user, group=group, mode=0o600) def generate(openvpn): interface = openvpn['ifname'] directory = os.path.dirname(cfg_file.format(**openvpn)) + # create base config directory on demand + makedir(directory, user, group) + # enforce proper permissions on /run/openvpn + chown(directory, user, group) # we can't know in advance which clients have been removed, # thus all client configs will be removed and re-added on demand @@ -543,9 +543,7 @@ def generate(openvpn): return None # create client config directory on demand - if not os.path.exists(ccd_dir): - os.makedirs(ccd_dir, 0o755) - chown(ccd_dir, user, group) + makedir(ccd_dir, user, group) # Fix file permissons for keys generate_pki_files(openvpn) -- cgit v1.2.3 From 2647edc30f1e02840cae62fde8b44345d35ac720 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 8 Sep 2021 14:35:20 +0200 Subject: openvpn: T3805: drop privileges using systemd - required for rtnetlink --- data/templates/openvpn/server.conf.tmpl | 2 -- src/conf_mode/interfaces-openvpn.py | 3 --- src/etc/systemd/system/openvpn@.service.d/override.conf | 4 ++++ 3 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 9b07a9ba2..9e4cc6813 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -7,8 +7,6 @@ # verb 3 -user {{ daemon_user }} -group {{ daemon_group }} dev-type {{ device_type }} dev {{ ifname }} persist-key diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index c837328be..bbf17ed5a 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -81,9 +81,6 @@ def get_config(config=None): openvpn['pki'] = tmp_pki openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) - openvpn['daemon_user'] = user - openvpn['daemon_group'] = group - return openvpn def is_ec_private_key(pki, cert_name): diff --git a/src/etc/systemd/system/openvpn@.service.d/override.conf b/src/etc/systemd/system/openvpn@.service.d/override.conf index 7946484a3..03fe6b587 100644 --- a/src/etc/systemd/system/openvpn@.service.d/override.conf +++ b/src/etc/systemd/system/openvpn@.service.d/override.conf @@ -7,3 +7,7 @@ WorkingDirectory= WorkingDirectory=/run/openvpn ExecStart= ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid +User=openvpn +Group=openvpn +AmbientCapabilities=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE +CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE -- cgit v1.2.3 From 588cc03a61414e8f9f35285b9b961c2004e24751 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 8 Sep 2021 14:36:06 +0200 Subject: openvpn: T3805: fix bool logic in verify_pki() for client mode Add support for OpenVPN client mode with only the CA certificate of the server installed. --- src/conf_mode/interfaces-openvpn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index bbf17ed5a..02b7f83bf 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -126,7 +126,7 @@ def verify_pki(openvpn): if tls['ca_certificate'] not in pki['ca']: raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') - if not (mode == 'client' and 'auth_key' in tls): + if mode != 'client' and 'auth_key' not in tls: if 'certificate' not in tls: raise ConfigError(f'Missing "tls certificate" on openvpn interface {interface}') -- cgit v1.2.3 From 3a0e586544fbe729f030d1950ea3ebebc454aa87 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Sep 2021 17:14:05 +0200 Subject: policy: T3812: FRR bgpd also knows about route-maps because of rpki In order to alter rpki configuration we must also process the route-map nodes with/for bgpd. --- src/conf_mode/policy.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index d56bae9e9..1a03d520b 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -190,6 +190,7 @@ def apply(policy): frr_cfg.modify_section(r'^bgp community-list .*') frr_cfg.modify_section(r'^bgp extcommunity-list .*') frr_cfg.modify_section(r'^bgp large-community-list .*') + frr_cfg.modify_section(r'^route-map .*') frr_cfg.add_before('^line vty', policy['new_frr_config']) frr_cfg.commit_configuration(bgp_daemon) -- cgit v1.2.3 From 2abf6711e880463b9d9276b2fa9ea7b4ebcdc179 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Sep 2021 16:46:55 +0200 Subject: ethernet: T3802: use only one implementation for get_driver_name() Move the two implementations to get the driver name of a NIC from ethernet.py and ethtool.py to only ethtool.py. (cherry picked from commit 07840977834816b69fa3b366817d90f44b5dc7a7) --- python/vyos/ethtool.py | 13 ++++++++----- python/vyos/ifconfig/ethernet.py | 28 +++------------------------- src/conf_mode/interfaces-ethernet.py | 2 +- 3 files changed, 12 insertions(+), 31 deletions(-) (limited to 'src/conf_mode') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 7e46969cf..4efc3a234 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -134,6 +134,12 @@ class Ethtool: # ['Autonegotiate:', 'on'] self._flow_control_enabled = out.splitlines()[1].split()[-1] + def get_auto_negotiation(self): + return self._auto_negotiation + + def get_driver_name(self): + return self._driver_name + def _get_generic(self, feature): """ Generic method to read self._features and return a tuple for feature @@ -189,7 +195,7 @@ class Ethtool: if duplex not in ['full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') - if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: return False if speed in self._speed_duplex: @@ -199,7 +205,7 @@ class Ethtool: def check_flow_control(self): """ Check if the NIC supports flow-control """ - if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: return False return self._flow_control @@ -208,6 +214,3 @@ class Ethtool: raise ValueError('Interface does not support changing '\ 'flow-control settings!') return self._flow_control_enabled - - def get_auto_negotiation(self): - return self._auto_negotiation diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index c4dfc7198..78fec1fa2 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -80,25 +80,6 @@ class EthernetIf(Interface): super().__init__(ifname, **kargs) self.ethtool = Ethtool(ifname) - def get_driver_name(self): - """ - Return the driver name used by NIC. Some NICs don't support all - features e.g. changing link-speed, duplex - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.get_driver_name() - 'vmxnet3' - """ - ifname = self.config['ifname'] - sysfs_file = f'/sys/class/net/{ifname}/device/driver/module' - if os.path.exists(sysfs_file): - link = os.readlink(sysfs_file) - return os.path.basename(link) - else: - return None - def set_flow_control(self, enable): """ Changes the pause parameters of the specified Ethernet device. @@ -116,8 +97,7 @@ class EthernetIf(Interface): raise ValueError("Value out of range") if not self.ethtool.check_flow_control(): - self._debug_msg(f'{driver_name} driver does not support changing '\ - 'flow control settings!') + self._debug_msg(f'NIC driver does not support changing flow control settings!') return False current = self.ethtool.get_flow_control() @@ -151,10 +131,8 @@ class EthernetIf(Interface): if duplex not in ['auto', 'full', 'half']: raise ValueError("Value out of range (duplex)") - driver_name = self.get_driver_name() - if driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: - self._debug_msg(f'{driver_name} driver does not support changing '\ - 'speed/duplex settings!') + if not self.ethtool.check_speed_duplex(speed, duplex): + self._debug_msg(f'NIC driver does not support changing speed/duplex settings!') return # Get current speed and duplex settings: diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 21a04f954..e7250fb49 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -126,7 +126,7 @@ def verify(ethernet): if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): raise ConfigError('Interface does not suport RPS!') - driver = EthernetIf(ifname).get_driver_name() + driver = ethtool.get_driver_name() # T3342 - Xen driver requires special treatment if driver == 'vif': if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: -- cgit v1.2.3 From c3e585ebd53a9e61303ffd7c13289ce328badb3d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 10 Sep 2021 15:28:48 +0000 Subject: squidguard: T3810: Set DB directory rigths 755 --- src/conf_mode/service_webproxy.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py index cbbd2e0bc..a16cc4aeb 100755 --- a/src/conf_mode/service_webproxy.py +++ b/src/conf_mode/service_webproxy.py @@ -23,6 +23,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render from vyos.util import call +from vyos.util import chmod_755 from vyos.util import dict_search from vyos.util import write_file from vyos.validate import is_addr_assigned @@ -192,6 +193,8 @@ def apply(proxy): return None + if os.path.exists(squidguard_db_dir): + chmod_755(squidguard_db_dir) call('systemctl restart squid.service') return None -- cgit v1.2.3 From 3e85333ae7c53fc8b2ceae1d1788e795fd92c939 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 15 Sep 2021 19:14:37 +0200 Subject: ipsec: T3830: "authentication id|use-x509-id" are mutually exclusive Manually set peer id and use-x509-id are mutually exclusive! --- data/templates/ipsec/swanctl/peer.tmpl | 2 +- src/conf_mode/vpn_ipsec.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index 5d69b3d66..98c09436c 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -31,7 +31,7 @@ encap = yes {% endif %} local { -{% if peer_conf.authentication is defined and peer_conf.authentication.id is defined and peer_conf.authentication.use_x509_id is not defined %} +{% if peer_conf.authentication is defined and peer_conf.authentication.id is defined and peer_conf.authentication.id is not none %} id = "{{ peer_conf.authentication.id }}" {% endif %} auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }} diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index ff6090e22..99b82ca2d 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -362,6 +362,9 @@ def verify(ipsec): if 'authentication' not in peer_conf or 'mode' not in peer_conf['authentication']: raise ConfigError(f"Missing authentication on site-to-site peer {peer}") + if {'id', 'use_x509_id'} <= set(peer_conf['authentication']): + raise ConfigError(f"Manually set peer id and use-x509-id are mutually exclusive!") + if peer_conf['authentication']['mode'] == 'x509': if 'x509' not in peer_conf['authentication']: raise ConfigError(f"Missing x509 settings on site-to-site peer {peer}") -- cgit v1.2.3 From a4440bd589db645eb99f343a8163e188a700774c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 18 Sep 2021 21:27:47 +0200 Subject: dhcp-server: T1968: allow multiple static-routes to be configured vyos@vyos# show service dhcp-server shared-network-name LAN { subnet 10.0.0.0/24 { default-router 10.0.0.1 dns-server 194.145.150.1 lease 88 range 0 { start 10.0.0.100 stop 10.0.0.200 } static-route 192.168.10.0/24 { next-hop 10.0.0.2 } static-route 192.168.20.0/24 { router 10.0.0.2 } } } --- data/templates/dhcp-server/dhcpd.conf.tmpl | 10 ++-- interface-definitions/dhcp-server.xml.in | 29 +++++------ smoketest/scripts/cli/test_service_dhcp-server.py | 3 +- src/conf_mode/dhcp_server.py | 7 +-- src/migration-scripts/dhcp-server/5-to-6 | 61 +++++++++++++++++++++++ 5 files changed, 85 insertions(+), 25 deletions(-) create mode 100755 src/migration-scripts/dhcp-server/5-to-6 (limited to 'src/conf_mode') diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index f0bfa468c..3ac92d3c9 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -110,9 +110,13 @@ shared-network {{ network | replace('_','-') }} { {% if subnet_config.default_router and subnet_config.default_router is not none %} {% set static_default_route = ', ' + '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %} {% endif %} -{% if subnet_config.static_route.router is defined and subnet_config.static_route.router is not none and subnet_config.static_route.destination_subnet is defined and subnet_config.static_route.destination_subnet is not none %} - option rfc3442-static-route {{ subnet_config.static_route.destination_subnet | isc_static_route(subnet_config.static_route.router) }}{{ static_default_route }}; - option windows-static-route {{ subnet_config.static_route.destination_subnet | isc_static_route(subnet_config.static_route.router) }}; +{% if subnet_config.static_route is defined and subnet_config.static_route is not none %} +{% set rfc3442_routes = [] %} +{% for route, route_options in subnet_config.static_route.items() %} +{% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %} +{% endfor %} + option rfc3442-static-route {{ rfc3442_routes | join(', ') }}{{ static_default_route }}; + option windows-static-route {{ rfc3442_routes | join(', ') }}; {% endif %} {% endif %} {% if subnet_config.ip_forwarding is defined %} diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index bafd6f6a2..c0f72dd86 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -357,26 +357,21 @@ - + - Classless static route + Classless static route destination subnet [REQUIRED] + + ipv4net + IPv4 address and prefix length + + + + - - - Destination subnet [REQUIRED] - - ipv4net - IPv4 address and prefix length - - - - - - - + - IP address of router to be used to reach the destination subnet [REQUIRED] + IP address of router to be used to reach the destination subnet ipv4 IPv4 address of router @@ -387,7 +382,7 @@ - + Additional subnet parameters for DHCP server. You must diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 815bd333a..40977bb04 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -123,8 +123,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['wpad-url', wpad]) self.cli_set(pool + ['server-identifier', server_identifier]) - self.cli_set(pool + ['static-route', 'destination-subnet', '10.0.0.0/24']) - self.cli_set(pool + ['static-route', 'router', '192.0.2.1']) + self.cli_set(pool + ['static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index cdee72e09..8d6cef8b7 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -159,9 +159,10 @@ def verify(dhcp): 'lease subnet must be configured.') for subnet, subnet_config in network_config['subnet'].items(): - if 'static_route' in subnet_config and len(subnet_config['static_route']) != 2: - raise ConfigError('Missing DHCP static-route parameter(s):\n' \ - 'destination-subnet | router must be defined!') + if 'static_route' in subnet_config: + for route, route_option in subnet_config['static_route'].items(): + if 'next_hop' not in route_option: + raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!') # Check if DHCP address range is inside configured subnet declaration if 'range' in subnet_config: diff --git a/src/migration-scripts/dhcp-server/5-to-6 b/src/migration-scripts/dhcp-server/5-to-6 new file mode 100755 index 000000000..4cd2ec07a --- /dev/null +++ b/src/migration-scripts/dhcp-server/5-to-6 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + + +import sys +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['service', 'dhcp-server', 'shared-network-name'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# Run this for every instance if 'shared-network-name' +for network in config.list_nodes(base): + base_network = base + [network] + + if not config.exists(base_network + ['subnet']): + continue + + # Run this for every specified 'subnet' + for subnet in config.list_nodes(base_network + ['subnet']): + base_subnet = base_network + ['subnet', subnet] + + if config.exists(base_subnet + ['static-route']): + prefix = config.return_value(base_subnet + ['static-route', 'destination-subnet']) + router = config.return_value(base_subnet + ['static-route', 'router']) + config.delete(base_subnet + ['static-route']) + + config.set(base_subnet + ['static-route', prefix, 'next-hop'], value=router) + config.set_tag(base_subnet + ['static-route']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From ae2dc55aa68679e828d4bb133fc515172c081d0f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 18 Sep 2021 22:12:09 +0200 Subject: container: T2216: add IPv6 support to container networks --- interface-definitions/containers.xml.in | 8 +++- src/conf_mode/containers.py | 72 ++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 34 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in index d990e41a3..bf672307c 100644 --- a/interface-definitions/containers.xml.in +++ b/interface-definitions/containers.xml.in @@ -76,7 +76,8 @@ - Set IPv4 static address to container (optional) + + Assign static IP address to container ipv4 IPv4 address @@ -206,8 +207,13 @@ ipv4net IPv4 network prefix + + ipv6net + IPv6 network prefix + + diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 3a93bf062..1e0197a13 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -20,6 +20,7 @@ import json from ipaddress import ip_address from ipaddress import ip_network from time import sleep +from json import dumps as json_write from vyos.config import Config from vyos.configdict import dict_merge @@ -31,10 +32,10 @@ from vyos.util import read_file from vyos.util import write_file from vyos.util import is_systemd_service_active from vyos.util import is_systemd_service_running - -from vyos.template import render +from vyos.template import inc_ip from vyos.template import is_ipv4 from vyos.template import is_ipv6 +from vyos.template import render from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -185,6 +186,37 @@ def generate(container): if not container: return None + if 'network' in container: + for network, network_config in container['network'].items(): + tmp = { + 'cniVersion' : '0.4.0', + 'name' : network, + 'plugins' : [{ + 'type': 'bridge', + 'bridge': f'cni-{network}', + 'isGateway': True, + 'ipMasq': False, + 'hairpinMode': False, + 'ipam' : { + 'type': 'host-local', + 'routes': [], + 'ranges' : [], + }, + }] + } + + for prefix in network_config['prefix']: + net = [{'gateway' : inc_ip(prefix, 1), 'subnet' : prefix}] + tmp['plugins'][0]['ipam']['ranges'].append(net) + + # install per address-family default orutes + default_route = '0.0.0.0/0' + if is_ipv6(prefix): + default_route = '::/0' + tmp['plugins'][0]['ipam']['routes'].append({'dst': default_route}) + + write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2)) + render(config_containers_registry, 'containers/registry.tmpl', container) render(config_containers_storage, 'containers/storage.tmpl', container) @@ -201,7 +233,9 @@ def apply(container): # Delete old networks if needed if 'network_remove' in container: for network in container['network_remove']: - call(f'podman network rm --force {network}') + tmp = f'/etc/cni/net.d/{network}.conflist' + if os.path.exists(tmp): + os.unlink(tmp) service_name = 'podman.service' if 'network' in container or 'name' in container: @@ -214,35 +248,6 @@ def apply(container): else: _cmd(f'systemctl stop {service_name}') - - # Add network - if 'network' in container: - for network, network_config in container['network'].items(): - # Check if the network has already been created - if not network_exists(network) and 'prefix' in network_config: - tmp = f'podman network create {network}' - # we can not use list comprehension here as the --ipv6 option - # must immediately follow the specified subnet!!! - for prefix in sorted(network_config['prefix']): - tmp += f' --subnet={prefix}' - if is_ipv6(prefix): - tmp += ' --ipv6' - _cmd(tmp) - - # Disable masquerading and use traditional bridging so VyOS - # can control firewalling/NAT by the real VyOS CLI - cni_network_config = f'/etc/cni/net.d/{network}.conflist' - tmp = read_file(cni_network_config) - config = json.loads(tmp) - if 'plugins' in config: - for count in range(0, len(config['plugins'])): - if 'ipMasq' in config['plugins'][count]: - config['plugins'][count]['ipMasq'] = False - if 'hairpinMode' in config['plugins'][count]: - config['plugins'][count]['hairpinMode'] = False - - write_file(cni_network_config, json.dumps(config, indent=4)) - # Add container if 'name' in container: for name, container_config in container['name'].items(): @@ -303,7 +308,8 @@ def apply(container): for network in container_config['network']: ipparam = '' if 'address' in container_config['network'][network]: - ipparam = '--ip ' + container_config['network'][network]['address'] + address = container_config['network'][network]['address'] + ipparam = f'--ip {address}' _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}') return None -- cgit v1.2.3 From a8ccf72c222caad8cd7aaca9bca773be39e87f5c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Sep 2021 10:51:15 +0200 Subject: dhcp-server: T3672: only one failover peer is supported --- data/templates/dhcp-server/dhcpd.conf.tmpl | 37 ++++------- interface-definitions/dhcp-server.xml.in | 98 ++++++++++++++---------------- src/conf_mode/dhcp_server.py | 36 +++++------ src/migration-scripts/dhcp-server/5-to-6 | 25 ++++++-- 4 files changed, 97 insertions(+), 99 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index 108c9cc85..54fff3ded 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -31,32 +31,25 @@ option wpad-url code 252 = text; {% endfor %} {% endif %} -{% if shared_network_name is defined and shared_network_name is not none %} -{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %} -{% if network_config.subnet is defined and network_config.subnet is not none %} -{% for subnet, subnet_config in network_config.subnet.items() %} -{% if subnet_config.failover is defined and subnet_config.failover is defined and subnet_config.failover.name is defined and subnet_config.failover.name is not none %} -# Failover configuration for {{ subnet }} -failover peer "{{ subnet_config.failover.name }}" { -{% if subnet_config.failover.status == 'primary' %} +{% if failover is defined and failover is not none %} +{% set dhcp_failover_name = 'VyOS-DHCP-failover-peer' %} +# DHCP failover configuration +failover peer "{{ dhcp_failover_name }}" { +{% if failover.status == 'primary' %} primary; mclt 1800; split 128; -{% elif subnet_config.failover.status == 'secondary' %} +{% elif failover.status == 'secondary' %} secondary; -{% endif %} - address {{ subnet_config.failover.local_address }}; +{% endif %} + address {{ failover.source_address }}; port 520; - peer address {{ subnet_config.failover.peer_address }}; + peer address {{ failover.remote }}; peer port 520; max-response-delay 30; max-unacked-updates 10; load balance max seconds 3; } -{% endif %} -{% endfor %} -{% endif %} -{% endfor %} {% endif %} {% if listen_address is defined and listen_address is not none %} @@ -184,23 +177,17 @@ shared-network {{ network | replace('_','-') }} { } {% endfor %} {% endif %} -{% if subnet_config.failover is defined and subnet_config.failover.name is defined and subnet_config.failover.name is not none %} pool { - failover peer "{{ subnet_config.failover.name }}"; +{% if subnet_config.enable_failover is defined %} + failover peer "{{ dhcp_failover_name }}"; deny dynamic bootp clients; +{% endif %} {% if subnet_config.range is defined and subnet_config.range is not none %} {% for range, range_options in subnet_config.range.items() %} range {{ range_options.start }} {{ range_options.stop }}; {% endfor %} {% endif %} } -{% else %} -{% if subnet_config.range is defined and subnet_config.range is not none %} -{% for range, range_options in subnet_config.range.items() %} - range {{ range_options.start }} {{ range_options.stop }}; -{% endfor %} -{% endif %} -{% endif %} } {% endfor %} {% endif %} diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index e629d96ab..960b8a4f0 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -16,6 +16,46 @@ + + + DHCP failover configuration + + + #include + + + IPv4 remote address used for connectio + + ipv4 + IPv4 address of failover peer + + + + + + + + + Failover hierarchy + + primary secondary + + + primary + Configure this server to be the primary node + + + secondary + Configure this server to be the secondary node + + + ^(primary|secondary)$ + + Invalid DHCP failover peer status + + + + Additional global parameters for DHCP server. You must @@ -118,6 +158,12 @@ #include #include #include + + + Enable DHCP failover support for this subnet + + + IP address to exclude from DHCP lease range @@ -131,58 +177,6 @@ - - - DHCP failover parameters - - - - - IP address for failover peer to connect [REQUIRED] - - ipv4 - IPv4 address to exclude from lease range - - - - - - - - - DHCP failover peer name [REQUIRED] - - [-_a-zA-Z0-9.]+ - - Invalid failover peer name. May only contain letters, numbers and .-_ - - - - - IP address of failover peer [REQUIRED] - - ipv4 - IPv4 address of failover peer - - - - - - - - - DHCP failover peer status (primary|secondary) [REQUIRED] - - primary secondary - - - ^(primary|secondary)$ - - Invalid DHCP failover peer status - - - - Enable IP forwarding on client diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 8d6cef8b7..5b3809017 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -148,9 +148,9 @@ def verify(dhcp): 'At least one DHCP shared network must be configured.') # Inspect shared-network/subnet - failover_names = [] listen_ok = False subnets = [] + failover_ok = False # A shared-network requires a subnet definition for network, network_config in dhcp['shared_network_name'].items(): @@ -159,11 +159,19 @@ def verify(dhcp): 'lease subnet must be configured.') for subnet, subnet_config in network_config['subnet'].items(): + # All delivered static routes require a next-hop to be set if 'static_route' in subnet_config: for route, route_option in subnet_config['static_route'].items(): if 'next_hop' not in route_option: raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!') + # DHCP failover needs at least one subnet that uses it + if 'enable_failover' in subnet_config: + if 'failover' not in dhcp: + raise ConfigError(f'Can not enable failover for "{subnet}" in "{network}".\n' \ + 'Failover is not configured globally!') + failover_ok = True + # Check if DHCP address range is inside configured subnet declaration if 'range' in subnet_config: networks = [] @@ -192,23 +200,6 @@ def verify(dhcp): tmp = IPRange(range_config['start'], range_config['stop']) networks.append(tmp) - if 'failover' in subnet_config: - for key in ['local_address', 'peer_address', 'name', 'status']: - if key not in subnet_config['failover']: - raise ConfigError(f'Missing DHCP failover parameter "{key}"!') - - # Failover names must be uniquie - if subnet_config['failover']['name'] in failover_names: - name = subnet_config['failover']['name'] - raise ConfigError(f'DHCP failover names must be unique:\n' \ - f'{name} has already been configured!') - failover_names.append(subnet_config['failover']['name']) - - # Failover requires start/stop ranges for pool - if 'range' not in subnet_config: - raise ConfigError(f'DHCP failover requires at least one start-stop range to be configured\n'\ - f'within shared-network "{network}, {subnet}" for using failover!') - # Exclude addresses must be in bound if 'exclude' in subnet_config: for exclude in subnet_config['exclude']: @@ -252,6 +243,15 @@ def verify(dhcp): if net.overlaps(net2): raise ConfigError('Conflicting subnet ranges: "{net}" overlaps "{net2}"!') + if 'failover' in dhcp: + if not failover_ok: + raise ConfigError('DHCP failover must be enabled for at least one subnet!') + + for key in ['source_address', 'remote', 'status']: + if key not in dhcp['failover']: + tmp = key.replace('_', '-') + raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!') + for address in (dict_search('listen_address', dhcp) or []): if is_addr_assigned(address): listen_ok = True diff --git a/src/migration-scripts/dhcp-server/5-to-6 b/src/migration-scripts/dhcp-server/5-to-6 index 7f447ac17..39bbb9f50 100755 --- a/src/migration-scripts/dhcp-server/5-to-6 +++ b/src/migration-scripts/dhcp-server/5-to-6 @@ -29,16 +29,16 @@ file_name = sys.argv[1] with open(file_name, 'r') as f: config_file = f.read() -base = ['service', 'dhcp-server', 'shared-network-name'] +base = ['service', 'dhcp-server'] config = ConfigTree(config_file) -if not config.exists(base): +if not config.exists(base + ['shared-network-name']): # Nothing to do exit(0) # Run this for every instance if 'shared-network-name' -for network in config.list_nodes(base): - base_network = base + [network] +for network in config.list_nodes(base + ['shared-network-name']): + base_network = base + ['shared-network-name', network] if not config.exists(base_network + ['subnet']): continue @@ -60,6 +60,23 @@ for network in config.list_nodes(base): if config.exists(base_subnet + ['dns-server']): config.rename(base_subnet + ['dns-server'], 'name-server') + + # T3672: ISC DHCP server only supports one failover peer + if config.exists(base_subnet + ['failover']): + # There can only be one failover configuration, if none is present + # we add the first one + if not config.exists(base + ['failover']): + local = config.return_value(base_subnet + ['failover', 'local-address']) + remote = config.return_value(base_subnet + ['failover', 'peer-address']) + status = config.return_value(base_subnet + ['failover', 'status']) + + config.set(base + ['failover', 'remote'], value=remote) + config.set(base + ['failover', 'source-address'], value=local) + config.set(base + ['failover', 'status'], value=status) + + config.delete(base_subnet + ['failover']) + config.set(base_subnet + ['enable-failover']) + try: with open(file_name, 'w') as f: f.write(config.to_string()) -- cgit v1.2.3 From 2985035bcb2f3732e15a41e3c2ee6c6c93a6836e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Sep 2021 11:32:04 +0200 Subject: dhcp-server: T3672: re-add missing "name" CLI option This option is mandatory and must be user configurable as it needs to match on both sides. --- data/templates/dhcp-server/dhcpd.conf.tmpl | 5 ++--- interface-definitions/dhcp-server.xml.in | 9 +++++++++ src/conf_mode/dhcp_server.py | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index c71934426..790f57bbb 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -32,9 +32,8 @@ option wpad-url code 252 = text; {% endif %} {% if failover is defined and failover is not none %} -{% set dhcp_failover_name = 'VyOS-DHCP-failover-peer' %} # DHCP failover configuration -failover peer "{{ dhcp_failover_name }}" { +failover peer "{{ failover.name }}" { {% if failover.status == 'primary' %} primary; mclt 1800; @@ -185,7 +184,7 @@ shared-network {{ network | replace('_','-') }} { {% endif %} pool { {% if subnet_config.enable_failover is defined %} - failover peer "{{ dhcp_failover_name }}"; + failover peer "{{ failover.name }}"; deny dynamic bootp clients; {% endif %} {% if subnet_config.range is defined and subnet_config.range is not none %} diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index 4551d75a9..2707ce96d 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -34,6 +34,15 @@ + + + Peer name used to identify connection + + [-_a-zA-Z0-9.]+ + + Invalid failover peer name. May only contain letters, numbers and .-_ + + Failover hierarchy diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 5b3809017..28f2a4ca5 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -247,7 +247,7 @@ def verify(dhcp): if not failover_ok: raise ConfigError('DHCP failover must be enabled for at least one subnet!') - for key in ['source_address', 'remote', 'status']: + for key in ['name', 'remote', 'source_address', 'status']: if key not in dhcp['failover']: tmp = key.replace('_', '-') raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!') -- cgit v1.2.3 From c1ac0630cfe0ee65569fbe435cc006ade20fed22 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 20:05:52 +0200 Subject: vrrp: keepalived: T2720: adjust to Jinja2 trim_blocks feature This is a successor to commit a2ac9fac16e ("vyos.template: T2720: always enable Jinja2 trim_blocks feature"). It only shifts the whitespaces / indents inside the keepalived configuration file. --- data/templates/vrrp/keepalived.conf.tmpl | 111 ++++++++++++++----------------- src/conf_mode/vrrp.py | 7 +- 2 files changed, 56 insertions(+), 62 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index c01101d85..2e2f62ae7 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -10,8 +10,7 @@ global_defs { } {% for group in groups %} - -{% if group.health_check_script %} +{% if group.health_check_script is defined and group.health_check_script is not none %} vrrp_script healthcheck_{{ group.name }} { script "{{ group.health_check_script }}" interval {{ group.health_check_interval }} @@ -19,88 +18,78 @@ vrrp_script healthcheck_{{ group.name }} { rise 1 } -{% endif %} +{% endif %} vrrp_instance {{ group.name }} { - {% if group.description %} - # {{ group.description }} - {% endif %} - + {{ '# ' ~ group.description if group.description is defined }} state BACKUP interface {{ group.interface }} virtual_router_id {{ group.vrid }} priority {{ group.priority }} advert_int {{ group.advertise_interval }} - - {% if group.preempt %} +{% if group.preempt is defined and group.preempt is not none %} preempt_delay {{ group.preempt_delay }} - {% else %} +{% else %} nopreempt - {% endif %} - - {% if group.peer_address %} +{% endif %} +{% if group.peer_address is defined and group.peer_address is not none %} unicast_peer { {{ group.peer_address }} } - {% endif %} - - {% if group.hello_source %} - {% if group.peer_address %} - unicast_src_ip {{ group.hello_source }} - {% else %} - mcast_src_ip {{ group.hello_source }} - {% endif %} - {% endif %} - - {% if group.use_vmac and group.peer_address %} - use_vmac {{group.interface}}v{{group.vrid}} - vmac_xmit_base - {% elif group.use_vmac %} - use_vmac {{group.interface}}v{{group.vrid}} - {% endif %} - - {% if group.auth_password %} - authentication { +{% endif %} +{% if group.hello_source is defined and group.hello_source is not none %} +{% if group.peer_address is defined and group.peer_address is not none %} + unicast_src_ip {{ group.hello_source }} +{% else %} + mcast_src_ip {{ group.hello_source }} +{% endif %} +{% endif %} +{% if group.use_vmac is defined and group.peer_address is defined %} + use_vmac {{ group.interface }}v{{ group.vrid }} + vmac_xmit_base +{% elif group.use_vmac is defined %} + use_vmac {{ group.interface }}v{{ group.vrid }} +{% endif %} +{% if group.auth_password is defined and group.auth_password is not none %} + authentication { auth_pass "{{ group.auth_password }}" auth_type {{ group.auth_type }} - } - {% endif %} - + } +{% endif %} +{% if group.virtual_addresses is defined and group.virtual_addresses is not none %} virtual_ipaddress { - {% for addr in group.virtual_addresses %} +{% for addr in group.virtual_addresses %} {{ addr }} - {% endfor %} +{% endfor %} } - - {% if group.virtual_addresses_excluded %} +{% endif %} +{% if group.virtual_addresses_excluded is defined and group.virtual_addresses_excluded is not none %} virtual_ipaddress_excluded { - {% for addr in group.virtual_addresses_excluded %} +{% for addr in group.virtual_addresses_excluded %} {{ addr }} - {% endfor %} +{% endfor %} } - {% endif %} - - {% if group.health_check_script %} +{% endif %} +{% if group.health_check_script is defined and group.health_check_script is not none %} track_script { healthcheck_{{ group.name }} } - {% endif %} +{% endif %} } - {% endfor %} -{% for sync_group in sync_groups %} +{% if sync_groups is defined and sync_groups is not none %} +{% for sync_group in sync_groups %} vrrp_sync_group {{ sync_group.name }} { - group { - {% for member in sync_group.members %} - {{ member }} - {% endfor %} - } - - {% if sync_group.conntrack_sync %} - {% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %} - notify_master "{{ vyos_helper }} master {{ sync_group.name }}" - notify_backup "{{ vyos_helper }} backup {{ sync_group.name }}" - notify_fault "{{ vyos_helper }} fault {{ sync_group.name }}" - {% endif %} + group { +{% for member in sync_group.members %} + {{ member }} +{% endfor %} + } +{% if sync_group.conntrack_sync %} +{% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %} + notify_master "{{ vyos_helper }} master {{ sync_group.name }}" + notify_backup "{{ vyos_helper }} backup {{ sync_group.name }}" + notify_fault "{{ vyos_helper }} fault {{ sync_group.name }}" +{% endif %} } - -{% endfor %} +{% endfor %} +{% endif %} diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 680a80859..2ece792dc 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -17,7 +17,12 @@ import os from sys import exit -from ipaddress import ip_address, ip_interface, IPv4Interface, IPv6Interface, IPv4Address, IPv6Address +from ipaddress import ip_address +from ipaddress import ip_interface +from ipaddress import IPv4Interface +from ipaddress import IPv6Interface +from ipaddress import IPv4Address +from ipaddress import IPv6Address from json import dumps from pathlib import Path -- cgit v1.2.3 From b243795eba1b36cadd81c3149e833bdf5c5bea70 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 20:29:36 +0200 Subject: vrrp: keepalived: T616: move configuration to volatile /run directory Move keepalived configuration from /etc/keepalived to /run/keepalived. --- data/templates/vrrp/keepalived.conf.tmpl | 3 +-- python/vyos/ifconfig/vrrp.py | 8 ++++---- smoketest/scripts/cli/test_ha_vrrp.py | 6 ++---- src/conf_mode/vrrp.py | 5 ++++- .../systemd/system/keepalived.service.d/override.conf | 10 ++++++++++ src/system/keepalived-fifo.py | 16 ++++++++-------- 6 files changed, 29 insertions(+), 19 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index 2e2f62ae7..2b53b04af 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -5,7 +5,7 @@ global_defs { dynamic_interfaces script_user root - notify_fifo /run/keepalived_notify_fifo + notify_fifo /run/keepalived/keepalived_notify_fifo notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py } @@ -16,7 +16,6 @@ vrrp_script healthcheck_{{ group.name }} { interval {{ group.health_check_interval }} fall {{ group.health_check_count }} rise 1 - } {% endif %} diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index b522cc1ab..481b0284a 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -32,14 +32,14 @@ class VRRPNoData(VRRPError): class VRRP(object): _vrrp_prefix = '00:00:5E:00:01:' location = { - 'pid': '/run/keepalived.pid', - 'fifo': '/run/keepalived_notify_fifo', + 'pid': '/run/keepalived/keepalived.pid', + 'fifo': '/run/keepalived/keepalived_notify_fifo', 'state': '/tmp/keepalived.data', 'stats': '/tmp/keepalived.stats', 'json': '/tmp/keepalived.json', 'daemon': '/etc/default/keepalived', - 'config': '/etc/keepalived/keepalived.conf', - 'vyos': '/run/keepalived_config.dict', + 'config': '/run/keepalived/keepalived.conf', + 'vyos': '/run/keepalived/keepalived_config.dict', } _signal = { diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index 03618c7d8..9c8d26699 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -14,22 +14,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import re import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file - from vyos.template import inc_ip PROCESS_NAME = 'keepalived' -KEEPALIVED_CONF = '/etc/keepalived/keepalived.conf' +KEEPALIVED_CONF = VRRP.location['config'] base_path = ['high-availability', 'vrrp'] vrrp_interface = 'eth1' diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 2ece792dc..f11dce879 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -30,6 +30,7 @@ import vyos.config from vyos import ConfigError from vyos.util import call +from vyos.util import makedir from vyos.template import render from vyos.ifconfig.vrrp import VRRP @@ -136,7 +137,9 @@ def get_config(config=None): sync_groups.append(sync_group) # create a file with dict with proposed configuration - with open("{}.temp".format(VRRP.location['vyos']), 'w') as dict_file: + dirname = os.path.dirname(VRRP.location['vyos']) + makedir(dirname) + with open(VRRP.location['vyos'] + ".temp", 'w') as dict_file: dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups})) return (vrrp_groups, sync_groups) diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf index 9fcabf652..e338b90a2 100644 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ b/src/etc/systemd/system/keepalived.service.d/override.conf @@ -1,2 +1,12 @@ +[Unit] +ConditionPathExists= +ConditionPathExists=/run/keepalived/keepalived.conf +After= +After=vyos-router.service + [Service] KillMode=process +ExecStart= +ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork $DAEMON_ARGS +PIDFile= +PIDFile=/run/keepalived/keepalived.pid diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 3b4330e9b..159fd0f54 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 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 @@ -13,7 +13,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# import os import time @@ -22,11 +21,12 @@ import argparse import threading import re import json -from pathlib import Path -from queue import Queue import logging + +from queue import Queue from logging.handlers import SysLogHandler +from vyos.ifconfig.vrrp import VRRP from vyos.util import cmd # configure logging @@ -62,7 +62,7 @@ class KeepalivedFifo: def _config_load(self): try: # read the dictionary file with configuration - with open('/run/keepalived_config.dict', 'r') as dict_file: + with open(VRRP.location['vyos'], 'r') as dict_file: vrrp_config_dict = json.load(dict_file) self.vrrp_config = {'vrrp_groups': {}, 'sync_groups': {}} # save VRRP instances to the new dictionary @@ -95,8 +95,8 @@ class KeepalivedFifo: # create FIFO pipe def pipe_create(self): - if Path(self.pipe_path).exists(): - logger.info("PIPE already exist: {}".format(self.pipe_path)) + if os.path.exists(self.pipe_path): + logger.info(f"PIPE already exist: {self.pipe_path}") else: os.mkfifo(self.pipe_path) @@ -135,7 +135,7 @@ class KeepalivedFifo: if n_type == 'GROUP': if os.path.exists(mdns_running_file): cmd(mdns_update_command) - + if n_name in self.vrrp_config['sync_groups'] and n_state in self.vrrp_config['sync_groups'][n_name]: n_script = self.vrrp_config['sync_groups'][n_name].get(n_state) if n_script: -- cgit v1.2.3 From 761631d662ccc18827d017d197c0e0100ac36219 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 20:31:06 +0200 Subject: vrrp: keepalived: T3847: migrate to get_config_dict() --- data/templates/vrrp/daemon.tmpl | 5 - data/templates/vrrp/keepalived.conf.tmpl | 121 ++++---- interface-definitions/vrrp.xml.in | 7 +- python/vyos/ifconfig/vrrp.py | 25 +- src/conf_mode/vrrp.py | 344 +++++++-------------- .../system/keepalived.service.d/override.conf | 3 +- src/system/keepalived-fifo.py | 73 ++--- 7 files changed, 237 insertions(+), 341 deletions(-) delete mode 100644 data/templates/vrrp/daemon.tmpl (limited to 'src/conf_mode') diff --git a/data/templates/vrrp/daemon.tmpl b/data/templates/vrrp/daemon.tmpl deleted file mode 100644 index c9dbea72d..000000000 --- a/data/templates/vrrp/daemon.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -# Autogenerated by VyOS -# Options to pass to keepalived - -# DAEMON_ARGS are appended to the keepalived command-line -DAEMON_ARGS="--snmp" diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index 3696c8395..a1e47abc3 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -12,85 +12,92 @@ global_defs { notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py } -{% for group in groups %} -{% if group.health_check_script is defined and group.health_check_script is not none %} -vrrp_script healthcheck_{{ group.name }} { - script "{{ group.health_check_script }}" - interval {{ group.health_check_interval }} - fall {{ group.health_check_count }} +{% if group is defined and group is not none %} +{% for name, group_config in group.items() if group_config.disable is not defined %} +{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %} +vrrp_script healthcheck_{{ name }} { + script "{{ group_config.health_check.script }}" + interval {{ group_config.health_check.interval }} + fall {{ group_config.health_check.failure_count }} rise 1 } -{% endif %} - -vrrp_instance {{ group.name }} { - {{ '# ' ~ group.description if group.description is defined }} +{% endif %} +vrrp_instance {{ name }} { + {{ '# ' ~ group_config.description if group_config.description is defined }} state BACKUP - interface {{ group.interface }} - virtual_router_id {{ group.vrid }} - priority {{ group.priority }} - advert_int {{ group.advertise_interval }} -{% if group.preempt is defined and group.preempt is not none %} - preempt_delay {{ group.preempt_delay }} -{% else %} + interface {{ group_config.interface }} + virtual_router_id {{ group_config.vrid }} + priority {{ group_config.priority }} + advert_int {{ group_config.advertise_interval }} +{% if group_config.no_preempt is not defined and group_config.preempt_delay is defined and group_config.preempt_delay is not none %} + preempt_delay {{ group_config.preempt_delay }} +{% elif group_config.no_preempt is defined %} nopreempt -{% endif %} -{% if group.peer_address is defined and group.peer_address is not none %} - unicast_peer { {{ group.peer_address }} } -{% endif %} -{% if group.hello_source is defined and group.hello_source is not none %} -{% if group.peer_address is defined and group.peer_address is not none %} - unicast_src_ip {{ group.hello_source }} -{% else %} - mcast_src_ip {{ group.hello_source }} {% endif %} -{% endif %} -{% if group.use_vmac is defined and group.peer_address is defined %} - use_vmac {{ group.interface }}v{{ group.vrid }} +{% if group_config.peer_address is defined and group_config.peer_address is not none %} + unicast_peer { {{ group_config.peer_address }} } +{% endif %} +{% if group_config.hello_source_address is defined and group_config.hello_source_address is not none %} +{% if group_config.peer_address is defined and group_config.peer_address is not none %} + unicast_src_ip {{ group_config.hello_source_address }} +{% else %} + mcast_src_ip {{ group_config.hello_source_address }} +{% endif %} +{% endif %} +{% if group_config.rfc3768_compatibility is defined and group_config.peer_address is defined %} + use_vmac {{ group_config.interface }}v{{ group_config.vrid }} vmac_xmit_base -{% elif group.use_vmac is defined %} - use_vmac {{ group.interface }}v{{ group.vrid }} -{% endif %} -{% if group.auth_password is defined and group.auth_password is not none %} +{% elif group_config.rfc3768_compatibility is defined %} + use_vmac {{ group_config.interface }}v{{ group_config.vrid }} +{% endif %} +{% if group_config.authentication is defined and group_config.authentication is not none %} authentication { - auth_pass "{{ group.auth_password }}" - auth_type {{ group.auth_type }} + auth_pass "{{ group_config.authentication.password }}" +{% if group_config.authentication.type == 'plaintext-password' %} + auth_type PASS +{% else %} + auth_type {{ group_config.authentication.type | upper }} +{% endif %} } -{% endif %} -{% if group.virtual_addresses is defined and group.virtual_addresses is not none %} +{% endif %} +{% if group_config.virtual_address is defined and group_config.virtual_address is not none %} virtual_ipaddress { -{% for addr in group.virtual_addresses %} +{% for addr in group_config.virtual_address %} {{ addr }} -{% endfor %} +{% endfor %} } -{% endif %} -{% if group.virtual_addresses_excluded is defined and group.virtual_addresses_excluded is not none %} +{% endif %} +{% if group_config.virtual_address_excluded is defined and group_config.virtual_address_excluded is not none %} virtual_ipaddress_excluded { -{% for addr in group.virtual_addresses_excluded %} +{% for addr in group_config.virtual_address_excluded %} {{ addr }} -{% endfor %} +{% endfor %} } -{% endif %} -{% if group.health_check_script is defined and group.health_check_script is not none %} +{% endif %} +{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %} track_script { - healthcheck_{{ group.name }} + healthcheck_{{ name }} } -{% endif %} +{% endif %} } -{% endfor %} +{% endfor %} +{% endif %} -{% if sync_groups is defined and sync_groups is not none %} -{% for sync_group in sync_groups %} -vrrp_sync_group {{ sync_group.name }} { +{% if sync_group is defined and sync_group is not none %} +{% for name, group_config in sync_group.items() if group_config.disable is not defined %} +vrrp_sync_group {{ name }} { group { -{% for member in sync_group.members %} +{% if group_config.member is defined and group_config.member is not none %} +{% for member in group_config.member %} {{ member }} -{% endfor %} +{% endfor %} +{% endif %} } -{% if sync_group.conntrack_sync %} +{% if conntrack_sync_group is defined and conntrack_sync_group == name %} {% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %} - notify_master "{{ vyos_helper }} master {{ sync_group.name }}" - notify_backup "{{ vyos_helper }} backup {{ sync_group.name }}" - notify_fault "{{ vyos_helper }} fault {{ sync_group.name }}" + notify_master "{{ vyos_helper }} master {{ name }}" + notify_backup "{{ vyos_helper }} backup {{ name }}" + notify_fault "{{ vyos_helper }} fault {{ name }}" {% endif %} } {% endfor %} diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in index 69a37239c..d57d213d9 100644 --- a/interface-definitions/vrrp.xml.in +++ b/interface-definitions/vrrp.xml.in @@ -35,6 +35,7 @@ + 1 @@ -94,6 +95,7 @@ + 3 @@ -102,6 +104,7 @@ + 60 @@ -164,6 +167,7 @@ + 0 @@ -176,11 +180,12 @@ + 100 - Use VRRP virtual MAC address as per RFC3768 + diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index 481b0284a..47aaadecd 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -22,6 +22,7 @@ from time import sleep from tabulate import tabulate from vyos import util +from vyos.configquery import ConfigTreeQuery class VRRPError(Exception): pass @@ -39,7 +40,6 @@ class VRRP(object): 'json': '/tmp/keepalived.json', 'daemon': '/etc/default/keepalived', 'config': '/run/keepalived/keepalived.conf', - 'vyos': '/run/keepalived/keepalived_config.dict', } _signal = { @@ -111,17 +111,20 @@ class VRRP(object): @classmethod def disabled(cls): - if not os.path.exists(cls.location['vyos']): - return [] - disabled = [] - config = json.loads(util.read_file(cls.location['vyos'])) - - # add disabled groups to the list - for group in config['vrrp_groups']: - if group['disable']: - disabled.append( - [group['name'], group['interface'], group['vrid'], 'DISABLED', '']) + base = ['high-availability', 'vrrp'] + conf = ConfigTreeQuery() + if conf.exists(base): + # Read VRRP configuration directly from CLI + vrrp_config_dict = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + + # add disabled groups to the list + if 'group' in vrrp_config_dict: + for group, group_config in vrrp_config_dict['group'].items(): + if 'disable' not in group_config: + continue + disabled.append([group, group_config['interface'], group_config['vrid'], 'DISABLED', '']) # return list with disabled instances return disabled diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index f11dce879..8bb237102 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2021 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 @@ -17,252 +17,136 @@ import os from sys import exit -from ipaddress import ip_address from ipaddress import ip_interface from ipaddress import IPv4Interface from ipaddress import IPv6Interface -from ipaddress import IPv4Address -from ipaddress import IPv6Address -from json import dumps -from pathlib import Path - -import vyos.config - -from vyos import ConfigError -from vyos.util import call -from vyos.util import makedir -from vyos.template import render +from vyos.config import Config +from vyos.configdict import dict_merge from vyos.ifconfig.vrrp import VRRP - +from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(config=None): - vrrp_groups = [] - sync_groups = [] - if config: - config = config + conf = config else: - config = vyos.config.Config() - - # Get the VRRP groups - for group_name in config.list_nodes("high-availability vrrp group"): - config.set_level("high-availability vrrp group {0}".format(group_name)) - - # Retrieve the values - group = {"preempt": True, "use_vmac": False, "disable": False} - - if config.exists("disable"): - group["disable"] = True - - group["name"] = group_name - group["vrid"] = config.return_value("vrid") - group["interface"] = config.return_value("interface") - group["description"] = config.return_value("description") - group["advertise_interval"] = config.return_value("advertise-interval") - group["priority"] = config.return_value("priority") - group["hello_source"] = config.return_value("hello-source-address") - group["peer_address"] = config.return_value("peer-address") - group["sync_group"] = config.return_value("sync-group") - group["preempt_delay"] = config.return_value("preempt-delay") - group["virtual_addresses"] = config.return_values("virtual-address") - group["virtual_addresses_excluded"] = config.return_values("virtual-address-excluded") - - group["auth_password"] = config.return_value("authentication password") - group["auth_type"] = config.return_value("authentication type") - - group["health_check_script"] = config.return_value("health-check script") - group["health_check_interval"] = config.return_value("health-check interval") - group["health_check_count"] = config.return_value("health-check failure-count") - - group["master_script"] = config.return_value("transition-script master") - group["backup_script"] = config.return_value("transition-script backup") - group["fault_script"] = config.return_value("transition-script fault") - group["stop_script"] = config.return_value("transition-script stop") - group["script_mode_force"] = config.exists("transition-script mode-force") - - if config.exists("no-preempt"): - group["preempt"] = False - if config.exists("rfc3768-compatibility"): - group["use_vmac"] = True - - # Substitute defaults where applicable - if not group["advertise_interval"]: - group["advertise_interval"] = 1 - if not group["priority"]: - group["priority"] = 100 - if not group["preempt_delay"]: - group["preempt_delay"] = 0 - if not group["health_check_interval"]: - group["health_check_interval"] = 60 - if not group["health_check_count"]: - group["health_check_count"] = 3 - - # FIXUP: translate our option for auth type to keepalived's syntax - # for simplicity - if group["auth_type"]: - if group["auth_type"] == "plaintext-password": - group["auth_type"] = "PASS" - else: - group["auth_type"] = "AH" - - vrrp_groups.append(group) - - config.set_level("") - - # Get the sync group used for conntrack-sync - conntrack_sync_group = None - if config.exists("service conntrack-sync failover-mechanism vrrp"): - conntrack_sync_group = config.return_value("service conntrack-sync failover-mechanism vrrp sync-group") - - # Get the sync groups - for sync_group_name in config.list_nodes("high-availability vrrp sync-group"): - config.set_level("high-availability vrrp sync-group {0}".format(sync_group_name)) - - sync_group = {"conntrack_sync": False} - sync_group["name"] = sync_group_name - sync_group["members"] = config.return_values("member") - if conntrack_sync_group: - if conntrack_sync_group == sync_group_name: - sync_group["conntrack_sync"] = True - - # add transition script configuration - sync_group["master_script"] = config.return_value("transition-script master") - sync_group["backup_script"] = config.return_value("transition-script backup") - sync_group["fault_script"] = config.return_value("transition-script fault") - sync_group["stop_script"] = config.return_value("transition-script stop") - - sync_groups.append(sync_group) - - # create a file with dict with proposed configuration - dirname = os.path.dirname(VRRP.location['vyos']) - makedir(dirname) - with open(VRRP.location['vyos'] + ".temp", 'w') as dict_file: - dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups})) - - return (vrrp_groups, sync_groups) - - -def verify(data): - vrrp_groups, sync_groups = data - - for group in vrrp_groups: - # Check required fields - if not group["vrid"]: - raise ConfigError("vrid is required but not set in VRRP group {0}".format(group["name"])) - if not group["interface"]: - raise ConfigError("interface is required but not set in VRRP group {0}".format(group["name"])) - if not group["virtual_addresses"]: - raise ConfigError("virtual-address is required but not set in VRRP group {0}".format(group["name"])) - - if group["auth_password"] and (not group["auth_type"]): - raise ConfigError("authentication type is required but not set in VRRP group {0}".format(group["name"])) - - # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction - - # XXX: filter on map object is destructive, so we force it to list. - # Additionally, filter objects always evaluate to True, empty or not, - # so we force them to lists as well. - vaddrs = list(map(lambda i: ip_interface(i), group["virtual_addresses"])) - vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) - vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs)) - - if vaddrs4 and vaddrs6: - raise ConfigError("VRRP group {0} mixes IPv4 and IPv6 virtual addresses, this is not allowed. Create separate groups for IPv4 and IPv6".format(group["name"])) - - if vaddrs4: - if group["hello_source"]: - hsa = ip_address(group["hello_source"]) - if isinstance(hsa, IPv6Address): - raise ConfigError("VRRP group {0} uses IPv4 but its hello-source-address is IPv6".format(group["name"])) - if group["peer_address"]: - pa = ip_address(group["peer_address"]) - if isinstance(pa, IPv6Address): - raise ConfigError("VRRP group {0} uses IPv4 but its peer-address is IPv6".format(group["name"])) - - if vaddrs6: - if group["hello_source"]: - hsa = ip_address(group["hello_source"]) - if isinstance(hsa, IPv4Address): - raise ConfigError("VRRP group {0} uses IPv6 but its hello-source-address is IPv4".format(group["name"])) - if group["peer_address"]: - pa = ip_address(group["peer_address"]) - if isinstance(pa, IPv4Address): - raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"])) - - # Warn the user about the deprecated mode-force option - if group['script_mode_force']: - print("""Warning: "transition-script mode-force" VRRP option is deprecated and will be removed in VyOS 1.4.""") - print("""It's no longer necessary, so you can safely remove it from your config now.""") - - # Disallow same VRID on multiple interfaces - _groups = sorted(vrrp_groups, key=(lambda x: x["interface"])) - count = len(_groups) - 1 - index = 0 - while (index < count): - if (_groups[index]["vrid"] == _groups[index + 1]["vrid"]) and (_groups[index]["interface"] == _groups[index + 1]["interface"]): - raise ConfigError("VRID {0} is used in groups {1} and {2} that both use interface {3}. Groups on the same interface must use different VRIDs".format( - _groups[index]["vrid"], _groups[index]["name"], _groups[index + 1]["name"], _groups[index]["interface"])) - else: - index += 1 + conf = Config() + + base = ['high-availability', 'vrrp'] + if not conf.exists(base): + return None + + vrrp = 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. + if 'group' in vrrp: + default_values = defaults(base + ['group']) + for group in vrrp['group']: + vrrp['group'][group] = dict_merge(default_values, vrrp['group'][group]) + + ## Get the sync group used for conntrack-sync + conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'] + if conf.exists(conntrack_path): + vrrp['conntrack_sync_group'] = conf.return_value(conntrack_path) + + return vrrp + +def verify(vrrp): + if not vrrp: + return None + + used_vrid_if = [] + if 'group' in vrrp: + for group, group_config in vrrp['group'].items(): + # Check required fields + if 'vrid' not in group_config: + raise ConfigError(f'VRID is required but not set in VRRP group "{group}"') + + if 'interface' not in group_config: + raise ConfigError(f'Interface is required but not set in VRRP group "{group}"') + + if 'virtual_address' not in group_config: + raise ConfigError(f'virtual-address is required but not set in VRRP group "{group}"') + + if 'authentication' in group_config: + if not {'password', 'type'} <= set(group_config['authentication']): + raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"') + + # We can not use a VRID once per interface + interface = group_config['interface'] + vrid = group_config['vrid'] + tmp = {'interface': interface, 'vrid': vrid} + if tmp in used_vrid_if: + raise ConfigError(f'VRID "{vrid}" can only be used once on interface "{interface}"!') + used_vrid_if.append(tmp) + + # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction + + # XXX: filter on map object is destructive, so we force it to list. + # Additionally, filter objects always evaluate to True, empty or not, + # so we force them to lists as well. + vaddrs = list(map(lambda i: ip_interface(i), group_config['virtual_address'])) + vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) + vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs)) + + if vaddrs4 and vaddrs6: + raise ConfigError(f'VRRP group "{group}" mixes IPv4 and IPv6 virtual addresses, this is not allowed.\n' \ + 'Create individual groups for IPv4 and IPv6!') + if vaddrs4: + if 'hello_source_address' in group_config: + if is_ipv6(group_config['hello_source_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!') + + if 'peer_address' in group_config: + if is_ipv6(group_config['peer_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!') + + if vaddrs6: + if 'hello_source_address' in group_config: + if is_ipv4(group_config['hello_source_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!') + + if 'peer_address' in group_config: + if is_ipv4(group_config['peer_address']): + raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!') + + # Warn the user about the deprecated mode-force option + if 'transition_script' in group_config and 'mode_force' in group_config['transition_script']: + print("""Warning: "transition-script mode-force" VRRP option is deprecated and will be removed in VyOS 1.4.""") + print("""It's no longer necessary, so you can safely remove it from your config now.""") # Check sync groups - vrrp_group_names = list(map(lambda x: x["name"], vrrp_groups)) - - for sync_group in sync_groups: - for m in sync_group["members"]: - if not (m in vrrp_group_names): - raise ConfigError("VRRP sync-group {0} refers to VRRP group {1}, but group {1} does not exist".format(sync_group["name"], m)) - - -def generate(data): - vrrp_groups, sync_groups = data - - # Remove disabled groups from the sync group member lists - for sync_group in sync_groups: - for member in sync_group["members"]: - g = list(filter(lambda x: x["name"] == member, vrrp_groups))[0] - if g["disable"]: - print("Warning: ignoring disabled VRRP group {0} in sync-group {1}".format(g["name"], sync_group["name"])) - # Filter out disabled groups - vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups)) - - render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', - {"groups": vrrp_groups, "sync_groups": sync_groups}) - render(VRRP.location['daemon'], 'vrrp/daemon.tmpl', {}) + if 'sync_group' in vrrp: + for sync_group, sync_config in vrrp['sync_group'].items(): + if 'member' in sync_config: + for member in sync_config['member']: + if member not in vrrp['group']: + raise ConfigError(f'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '\ + 'but it does not exist!') + +def generate(vrrp): + if not vrrp: + return None + + render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', vrrp) return None +def apply(vrrp): + service_name = 'keepalived.service' + if not vrrp: + call(f'systemctl stop {service_name}') + return None -def apply(data): - vrrp_groups, sync_groups = data - if vrrp_groups: - # safely rename a temporary file with configuration dict - try: - dict_file = Path("{}.temp".format(VRRP.location['vyos'])) - dict_file.rename(Path(VRRP.location['vyos'])) - except Exception as err: - print("Unable to rename the file with keepalived config for FIFO pipe: {}".format(err)) - - if not VRRP.is_running(): - print("Starting the VRRP process") - ret = call("systemctl restart keepalived.service") - else: - print("Reloading the VRRP process") - ret = call("systemctl reload keepalived.service") - - if ret != 0: - raise ConfigError("keepalived failed to start") - else: - # VRRP is removed in the commit - print("Stopping the VRRP process") - call("systemctl stop keepalived.service") - os.unlink(VRRP.location['daemon']) - + call(f'systemctl restart {service_name}') return None - if __name__ == '__main__': try: c = get_config() @@ -270,5 +154,5 @@ if __name__ == '__main__': generate(c) apply(c) except ConfigError as e: - print("VRRP error: {0}".format(str(e))) + print(e) exit(1) diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf index e338b90a2..1c68913f2 100644 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ b/src/etc/systemd/system/keepalived.service.d/override.conf @@ -6,7 +6,8 @@ After=vyos-router.service [Service] KillMode=process +EnvironmentFile= ExecStart= -ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork $DAEMON_ARGS +ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp PIDFile= PIDFile=/run/keepalived/keepalived.pid diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 159fd0f54..1fba0d75b 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -27,6 +27,7 @@ from queue import Queue from logging.handlers import SysLogHandler from vyos.ifconfig.vrrp import VRRP +from vyos.configquery import ConfigTreeQuery from vyos.util import cmd # configure logging @@ -44,12 +45,13 @@ mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns-repeater.py class KeepalivedFifo: # init - read command arguments def __init__(self): - logger.info("Starting FIFO pipe for Keepalived") + logger.info('Starting FIFO pipe for Keepalived') # define program arguments cmd_args_parser = argparse.ArgumentParser(description='Create FIFO pipe for keepalived and process notify events', add_help=False) cmd_args_parser.add_argument('PIPE', help='path to the FIFO pipe') # parse arguments cmd_args = cmd_args_parser.parse_args() + self._config_load() self.pipe_path = cmd_args.PIPE @@ -61,33 +63,34 @@ class KeepalivedFifo: # load configuration def _config_load(self): try: - # read the dictionary file with configuration - with open(VRRP.location['vyos'], 'r') as dict_file: - vrrp_config_dict = json.load(dict_file) + base = ['high-availability', 'vrrp'] + conf = ConfigTreeQuery() + if not conf.exists(base): + raise ValueError() + + # Read VRRP configuration directly from CLI + vrrp_config_dict = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) self.vrrp_config = {'vrrp_groups': {}, 'sync_groups': {}} - # save VRRP instances to the new dictionary - for vrrp_group in vrrp_config_dict['vrrp_groups']: - self.vrrp_config['vrrp_groups'][vrrp_group['name']] = { - 'STOP': vrrp_group.get('stop_script'), - 'FAULT': vrrp_group.get('fault_script'), - 'BACKUP': vrrp_group.get('backup_script'), - 'MASTER': vrrp_group.get('master_script') - } - # save VRRP sync groups to the new dictionary - for sync_group in vrrp_config_dict['sync_groups']: - self.vrrp_config['sync_groups'][sync_group['name']] = { - 'STOP': sync_group.get('stop_script'), - 'FAULT': sync_group.get('fault_script'), - 'BACKUP': sync_group.get('backup_script'), - 'MASTER': sync_group.get('master_script') - } - logger.debug("Loaded configuration: {}".format(self.vrrp_config)) + for key in ['group', 'sync_group']: + if key not in vrrp_config_dict: + continue + for group, group_config in vrrp_config_dict[key].items(): + if 'transition_script' not in group_config: + continue + self.vrrp_config['vrrp_groups'][group] = { + 'STOP': group_config['transition_script'].get('stop'), + 'FAULT': group_config['transition_script'].get('fault'), + 'BACKUP': group_config['transition_script'].get('backup'), + 'MASTER': group_config['transition_script'].get('master'), + } + logger.info(f'Loaded configuration: {self.vrrp_config}') except Exception as err: - logger.error("Unable to load configuration: {}".format(err)) + logger.error(f'Unable to load configuration: {err}') # run command def _run_command(self, command): - logger.debug("Running the command: {}".format(command)) + logger.debug(f'Running the command: {command}') try: cmd(command) except OSError as err: @@ -96,13 +99,13 @@ class KeepalivedFifo: # create FIFO pipe def pipe_create(self): if os.path.exists(self.pipe_path): - logger.info(f"PIPE already exist: {self.pipe_path}") + logger.info(f'PIPE already exist: {self.pipe_path}') else: os.mkfifo(self.pipe_path) # process message from pipe def pipe_process(self): - logger.debug("Message processing start") + logger.debug('Message processing start') regex_notify = re.compile(r'^(?P\w+) "(?P[\w-]+)" (?P\w+) (?P\d+)$', re.MULTILINE) while self.stopme.is_set() is False: # wait for a new message event from pipe_wait @@ -113,14 +116,14 @@ class KeepalivedFifo: # get all messages from queue and try to process them while self.message_queue.empty() is not True: message = self.message_queue.get() - logger.debug("Received message: {}".format(message)) + logger.debug(f'Received message: {message}') notify_message = regex_notify.search(message) # try to process a message if it looks valid if notify_message: n_type = notify_message.group('type') n_name = notify_message.group('name') n_state = notify_message.group('state') - logger.info("{} {} changed state to {}".format(n_type, n_name, n_state)) + logger.info(f'{n_type} {n_name} changed state to {n_state}') # check and run commands for VRRP instances if n_type == 'INSTANCE': if os.path.exists(mdns_running_file): @@ -143,16 +146,16 @@ class KeepalivedFifo: # mark task in queue as done self.message_queue.task_done() except Exception as err: - logger.error("Error processing message: {}".format(err)) - logger.debug("Terminating messages processing thread") + logger.error(f'Error processing message: {err}') + logger.debug('Terminating messages processing thread') # wait for messages def pipe_wait(self): - logger.debug("Message reading start") + logger.debug('Message reading start') self.pipe_read = os.open(self.pipe_path, os.O_RDONLY | os.O_NONBLOCK) while self.stopme.is_set() is False: # sleep a bit to not produce 100% CPU load - time.sleep(0.1) + time.sleep(0.250) try: # try to read a message from PIPE message = os.read(self.pipe_read, 500) @@ -165,21 +168,19 @@ class KeepalivedFifo: except Exception as err: # ignore the "Resource temporarily unavailable" error if err.errno != 11: - logger.error("Error receiving message: {}".format(err)) + logger.error(f'Error receiving message: {err}') - logger.debug("Closing FIFO pipe") + logger.debug('Closing FIFO pipe') os.close(self.pipe_read) - # handle SIGTERM signal to allow finish all messages processing def sigterm_handle(signum, frame): - logger.info("Ending processing: Received SIGTERM signal") + logger.info('Ending processing: Received SIGTERM signal') fifo.stopme.set() thread_wait_message.join() fifo.message_event.set() thread_process_message.join() - signal.signal(signal.SIGTERM, sigterm_handle) # init our class -- cgit v1.2.3 From 407db335b8eda0c9b8a090cde806a9107d3dbd96 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 22:00:09 +0200 Subject: vrrp: keepalived: T3847: remove "transition-script mode-force" option --- interface-definitions/vrrp.xml.in | 9 ------- src/conf_mode/vrrp.py | 6 ----- src/migration-scripts/vrrp/2-to-3 | 52 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 15 deletions(-) create mode 100755 src/migration-scripts/vrrp/2-to-3 (limited to 'src/conf_mode') diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in index cea60d9cb..d43ff83de 100644 --- a/interface-definitions/vrrp.xml.in +++ b/interface-definitions/vrrp.xml.in @@ -221,15 +221,6 @@ - - - - Disable VRRP state checking (deprecated, will be removed in VyOS 1.4) - - - - - diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 8bb237102..ba156e915 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -116,12 +116,6 @@ def verify(vrrp): if 'peer_address' in group_config: if is_ipv4(group_config['peer_address']): raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!') - - # Warn the user about the deprecated mode-force option - if 'transition_script' in group_config and 'mode_force' in group_config['transition_script']: - print("""Warning: "transition-script mode-force" VRRP option is deprecated and will be removed in VyOS 1.4.""") - print("""It's no longer necessary, so you can safely remove it from your config now.""") - # Check sync groups if 'sync_group' in vrrp: for sync_group, sync_config in vrrp['sync_group'].items(): diff --git a/src/migration-scripts/vrrp/2-to-3 b/src/migration-scripts/vrrp/2-to-3 new file mode 100755 index 000000000..b96ca139d --- /dev/null +++ b/src/migration-scripts/vrrp/2-to-3 @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +# T3847: vrrp config cleanup + +from sys import argv +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print('Must specify file name!') + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['high-availability', 'vrrp'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['group']): + for group in config.list_nodes(base + ['group']): + group_base = base + ['group', group] + + # Deprecated option + tmp = group_base + ['transition-script', 'mode-force'] + if config.exists(tmp): + config.delete(tmp) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) -- cgit v1.2.3 From b9d4d8c67d6b41ebbf5b570c7c6b09e05520e737 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 22:07:33 +0200 Subject: vrrp: keepalived: T3847: migrate/streamline CLI options Rename virtual-address -> address as we always talk about an IP address. --- data/templates/vrrp/keepalived.conf.tmpl | 8 ++++---- interface-definitions/vrrp.xml.in | 7 +++---- smoketest/scripts/cli/test_ha_vrrp.py | 6 +++--- src/conf_mode/vrrp.py | 6 +++--- src/migration-scripts/vrrp/2-to-3 | 10 ++++++++++ 5 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index a1e47abc3..e31bfaac0 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -60,16 +60,16 @@ vrrp_instance {{ name }} { {% endif %} } {% endif %} -{% if group_config.virtual_address is defined and group_config.virtual_address is not none %} +{% if group_config.address is defined and group_config.address is not none %} virtual_ipaddress { -{% for addr in group_config.virtual_address %} +{% for addr in group_config.address %} {{ addr }} {% endfor %} } {% endif %} -{% if group_config.virtual_address_excluded is defined and group_config.virtual_address_excluded is not none %} +{% if group_config.excluded_address is defined and group_config.excluded_address is not none %} virtual_ipaddress_excluded { -{% for addr in group_config.virtual_address_excluded %} +{% for addr in group_config.excluded_address %} {{ addr }} {% endfor %} } diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in index 44bd7e454..44a9a1f54 100644 --- a/interface-definitions/vrrp.xml.in +++ b/interface-definitions/vrrp.xml.in @@ -185,9 +185,9 @@ #include - + - Virtual address (IPv4 or IPv6, but they must not be mixed in one group) + Virtual IP address ipv4 IPv4 virtual address @@ -200,11 +200,10 @@ - Virtual address must be a valid IPv4 or IPv6 address with prefix length (e.g. 192.0.2.3/24 or 2001:db8:ff::10/64) - + Virtual address (If you need additional IPv4 and IPv6 in same group) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index 9c8d26699..4a743fe5e 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -59,7 +59,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.cli_set(group_base + ['description', group]) self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['virtual-address', vip]) + self.cli_set(group_base + ['address', vip]) self.cli_set(group_base + ['vrid', vlan_id]) # commit changes @@ -93,7 +93,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.cli_set(group_base + ['description', group]) self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['virtual-address', vip]) + self.cli_set(group_base + ['address', vip]) self.cli_set(group_base + ['vrid', vlan_id]) self.cli_set(group_base + ['advertise-interval', advertise_interval]) @@ -139,7 +139,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['virtual-address', vip]) + self.cli_set(group_base + ['address', vip]) self.cli_set(group_base + ['vrid', vlan_id]) self.cli_set(base_path + ['sync-group', sync_group, 'member', group]) diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index ba156e915..ffe9dd276 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -72,8 +72,8 @@ def verify(vrrp): if 'interface' not in group_config: raise ConfigError(f'Interface is required but not set in VRRP group "{group}"') - if 'virtual_address' not in group_config: - raise ConfigError(f'virtual-address is required but not set in VRRP group "{group}"') + if 'address' not in group_config: + raise ConfigError(f'Virtual IP address is required but not set in VRRP group "{group}"') if 'authentication' in group_config: if not {'password', 'type'} <= set(group_config['authentication']): @@ -92,7 +92,7 @@ def verify(vrrp): # XXX: filter on map object is destructive, so we force it to list. # Additionally, filter objects always evaluate to True, empty or not, # so we force them to lists as well. - vaddrs = list(map(lambda i: ip_interface(i), group_config['virtual_address'])) + vaddrs = list(map(lambda i: ip_interface(i), group_config['address'])) vaddrs4 = list(filter(lambda x: isinstance(x, IPv4Interface), vaddrs)) vaddrs6 = list(filter(lambda x: isinstance(x, IPv6Interface), vaddrs)) diff --git a/src/migration-scripts/vrrp/2-to-3 b/src/migration-scripts/vrrp/2-to-3 index b96ca139d..1151ae18c 100755 --- a/src/migration-scripts/vrrp/2-to-3 +++ b/src/migration-scripts/vrrp/2-to-3 @@ -44,6 +44,16 @@ if config.exists(base + ['group']): if config.exists(tmp): config.delete(tmp) + # Rename virtual-address -> address + tmp = group_base + ['virtual-address'] + if config.exists(tmp): + config.rename(tmp, 'address') + + # Rename virtual-address-excluded -> excluded-address + tmp = group_base + ['virtual-address-excluded'] + if config.exists(tmp): + config.rename(tmp, 'excluded-address') + try: with open(file_name, 'w') as f: f.write(config.to_string()) -- cgit v1.2.3 From 3114c7ccbdf867f9cdaa436792937b16ea4d85bf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 22 Sep 2021 08:18:42 +0200 Subject: vrrp: keepalived: T3847: enable no_tag_node_value_mangle for get_config_dict() Commit 761631d6 ("vrrp: keepalived: T3847: migrate to get_config_dict()") switched to the new python function get_config_dict(), when we deal with tag nodes that can contain a hyphen, we should also set no_tag_node_value_mangle in order to preserve it. This caused a dict lookup error as the hyphens in the test scripts got replaced by an _. --- data/templates/vrrp/keepalived.conf.tmpl | 4 +++- src/conf_mode/vrrp.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index e31bfaac0..b4824a994 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -23,7 +23,9 @@ vrrp_script healthcheck_{{ name }} { } {% endif %} vrrp_instance {{ name }} { - {{ '# ' ~ group_config.description if group_config.description is defined }} +{% if group_config.description is defined and group_config.description is not none %} + # {{ group_config.description }} +{% endif %} state BACKUP interface {{ group_config.interface }} virtual_router_id {{ group_config.vrid }} diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index ffe9dd276..e8f1c1f99 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -43,7 +43,8 @@ def get_config(config=None): if not conf.exists(base): return None - vrrp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + vrrp = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. if 'group' in vrrp: -- cgit v1.2.3 From 4084046987ab52f8c77b0393c1820d37a2124bbd Mon Sep 17 00:00:00 2001 From: Nicolas Riebesel Date: Thu, 23 Sep 2021 01:28:22 +0200 Subject: openvpn: T3642: Fix password_protected check --- src/conf_mode/interfaces-openvpn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 02b7f83bf..ce62a8b82 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -134,7 +134,7 @@ def verify_pki(openvpn): if tls['certificate'] not in pki['certificate']: raise ConfigError(f'Invalid certificate on openvpn interface {interface}') - if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected'): + if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None: raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}') if mode == 'server' and 'dh_params' not in tls and not is_ec_private_key(pki, tls['certificate']): -- cgit v1.2.3 From f4732f348ff4b0a9d82ec698f4a8b3c6107f9101 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 27 Sep 2021 20:43:02 +0200 Subject: frr: T2175: rename daemon Jinja2 templates to match (d)aemon suffix --- data/templates/frr/bfd.frr.tmpl | 44 ----------------- data/templates/frr/bfdd.frr.tmpl | 44 +++++++++++++++++ data/templates/frr/rip.frr.tmpl | 96 ------------------------------------- data/templates/frr/ripd.frr.tmpl | 96 +++++++++++++++++++++++++++++++++++++ data/templates/frr/ripng.frr.tmpl | 60 ----------------------- data/templates/frr/ripngd.frr.tmpl | 60 +++++++++++++++++++++++ data/templates/frr/static.frr.tmpl | 49 ------------------- data/templates/frr/staticd.frr.tmpl | 49 +++++++++++++++++++ src/conf_mode/protocols_bfd.py | 2 +- src/conf_mode/protocols_rip.py | 2 +- src/conf_mode/protocols_ripng.py | 2 +- src/conf_mode/protocols_static.py | 2 +- 12 files changed, 253 insertions(+), 253 deletions(-) delete mode 100644 data/templates/frr/bfd.frr.tmpl create mode 100644 data/templates/frr/bfdd.frr.tmpl delete mode 100644 data/templates/frr/rip.frr.tmpl create mode 100644 data/templates/frr/ripd.frr.tmpl delete mode 100644 data/templates/frr/ripng.frr.tmpl create mode 100644 data/templates/frr/ripngd.frr.tmpl delete mode 100644 data/templates/frr/static.frr.tmpl create mode 100644 data/templates/frr/staticd.frr.tmpl (limited to 'src/conf_mode') diff --git a/data/templates/frr/bfd.frr.tmpl b/data/templates/frr/bfd.frr.tmpl deleted file mode 100644 index 16f8be92c..000000000 --- a/data/templates/frr/bfd.frr.tmpl +++ /dev/null @@ -1,44 +0,0 @@ -! -bfd -{% if profile is defined and profile is not none %} -{% for profile_name, profile_config in profile.items() %} - profile {{ profile_name }} - detect-multiplier {{ profile_config.interval.multiplier }} - receive-interval {{ profile_config.interval.receive }} - transmit-interval {{ profile_config.interval.transmit }} -{% if profile_config.interval['echo-interval'] is defined and profile_config.interval['echo-interval'] is not none %} - echo-interval {{ profile_config.interval['echo-interval'] }} -{% endif %} -{% if profile_config['echo-mode'] is defined %} - echo-mode -{% endif %} -{% if profile_config.shutdown is defined %} - shutdown -{% else %} - no shutdown -{% endif %} - exit -{% endfor %} -{% endif %} -{% if peer is defined and peer is not none %} -{% for peer_name, peer_config in peer.items() %} - peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} - detect-multiplier {{ peer_config.interval.multiplier }} - receive-interval {{ peer_config.interval.receive }} - transmit-interval {{ peer_config.interval.transmit }} -{% if peer_config.interval['echo-interval'] is defined and peer_config.interval['echo-interval'] is not none %} - echo-interval {{ peer_config.interval['echo-interval'] }} -{% endif %} -{% if peer_config['echo-mode'] is defined %} - echo-mode -{% endif %} -{% if peer_config.shutdown is defined %} - shutdown -{% else %} - no shutdown -{% endif %} - exit -{% endfor %} -{% endif %} - end -! diff --git a/data/templates/frr/bfdd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl new file mode 100644 index 000000000..16f8be92c --- /dev/null +++ b/data/templates/frr/bfdd.frr.tmpl @@ -0,0 +1,44 @@ +! +bfd +{% if profile is defined and profile is not none %} +{% for profile_name, profile_config in profile.items() %} + profile {{ profile_name }} + detect-multiplier {{ profile_config.interval.multiplier }} + receive-interval {{ profile_config.interval.receive }} + transmit-interval {{ profile_config.interval.transmit }} +{% if profile_config.interval['echo-interval'] is defined and profile_config.interval['echo-interval'] is not none %} + echo-interval {{ profile_config.interval['echo-interval'] }} +{% endif %} +{% if profile_config['echo-mode'] is defined %} + echo-mode +{% endif %} +{% if profile_config.shutdown is defined %} + shutdown +{% else %} + no shutdown +{% endif %} + exit +{% endfor %} +{% endif %} +{% if peer is defined and peer is not none %} +{% for peer_name, peer_config in peer.items() %} + peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} + detect-multiplier {{ peer_config.interval.multiplier }} + receive-interval {{ peer_config.interval.receive }} + transmit-interval {{ peer_config.interval.transmit }} +{% if peer_config.interval['echo-interval'] is defined and peer_config.interval['echo-interval'] is not none %} + echo-interval {{ peer_config.interval['echo-interval'] }} +{% endif %} +{% if peer_config['echo-mode'] is defined %} + echo-mode +{% endif %} +{% if peer_config.shutdown is defined %} + shutdown +{% else %} + no shutdown +{% endif %} + exit +{% endfor %} +{% endif %} + end +! diff --git a/data/templates/frr/rip.frr.tmpl b/data/templates/frr/rip.frr.tmpl deleted file mode 100644 index cabc236f0..000000000 --- a/data/templates/frr/rip.frr.tmpl +++ /dev/null @@ -1,96 +0,0 @@ -! -{# RIP key-chain definition #} -{% if interface is defined and interface is not none %} -{% for iface, iface_config in interface.items() %} -{% if iface_config.authentication is defined and iface_config.authentication.md5 is defined and iface_config.authentication.md5 is not none %} -key chain {{ iface }}-rip -{% for key_id, key_options in iface_config.authentication.md5.items() %} - key {{ key_id }} -{% if key_options.password is defined and key_options.password is not none %} - key-string {{ key_options.password }} -{% endif %} -{% endfor %} -{% endif %} -{% endfor %} -{% endif %} -! -{# Interface specific configuration #} -{% if interface is defined and interface is not none %} -{% for iface, iface_config in interface.items() %} -interface {{ iface }} -{% if iface_config.authentication is defined and iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} - ip rip authentication mode text - ip rip authentication string {{ iface_config.authentication.plaintext_password }} -{% elif iface_config.authentication is defined and iface_config.authentication.md5 is defined and iface_config.authentication.md5 is not none %} - ip rip authentication key-chain {{ iface }}-rip - ip rip authentication mode md5 -{% endif %} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.disable is defined %} - no ip rip split-horizon -{% endif %} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.poison_reverse is defined %} - ip rip split-horizon poisoned-reverse -{% endif %} -{% endfor %} -{% endif %} -! -router rip -{% if default_distance is defined and default_distance is not none %} - distance {{ default_distance }} -{% endif %} -{% if network_distance is defined and network_distance is not none %} -{% for network, network_config in network_distance.items() %} -{% if network_config.distance is defined and network_config.distance is not none %} - distance {{ network_config.distance }} {{ network }} -{% endif %} -{% endfor %} -{% endif %} -{% if neighbor is defined and neighbor is not none %} -{% for address in neighbor %} - neighbor {{ address }} -{% endfor %} -{% endif %} -{% if distribute_list is defined and distribute_list is not none %} -{% if distribute_list.access_list is defined and distribute_list.access_list is not none %} -{% if distribute_list.access_list.in is defined and distribute_list.access_list.in is not none %} - distribute-list {{ distribute_list.access_list.in }} in -{% endif %} -{% if distribute_list.access_list.out is defined and distribute_list.access_list.out is not none %} - distribute-list {{ distribute_list.access_list.out }} out -{% endif %} -{% endif %} -{% if distribute_list.interface is defined and distribute_list.interface is not none %} -{% for interface, interface_config in distribute_list.interface.items() %} -{% if interface_config.access_list is defined and interface_config.access_list is not none %} -{% if interface_config.access_list.in is defined and interface_config.access_list.in is not none %} - distribute-list {{ interface_config.access_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.access_list.out is defined and interface_config.access_list.out is not none %} - distribute-list {{ interface_config.access_list.out }} out {{ interface }} -{% endif %} -{% endif %} -{% if interface_config.prefix_list is defined and interface_config.prefix_list is not none %} -{% if interface_config.prefix_list.in is defined and interface_config.prefix_list.in is not none %} - distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.prefix_list.out is defined and interface_config.prefix_list.out is not none %} - distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} -{% if distribute_list.prefix_list is defined and distribute_list.prefix_list is not none %} -{% if distribute_list.prefix_list.in is defined and distribute_list.prefix_list.in is not none %} - distribute-list prefix {{ distribute_list.prefix_list.in }} in -{% endif %} -{% if distribute_list.prefix_list.out is defined and distribute_list.prefix_list.out is not none %} - distribute-list prefix {{ distribute_list.prefix_list.out }} out -{% endif %} -{% endif %} -{% endif %} -{% include 'frr/rip_ripng.frr.j2' %} -! -{% if route_map is defined and route_map is not none %} -ip protocol rip route-map {{ route_map }} -{% endif %} -! diff --git a/data/templates/frr/ripd.frr.tmpl b/data/templates/frr/ripd.frr.tmpl new file mode 100644 index 000000000..cabc236f0 --- /dev/null +++ b/data/templates/frr/ripd.frr.tmpl @@ -0,0 +1,96 @@ +! +{# RIP key-chain definition #} +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +{% if iface_config.authentication is defined and iface_config.authentication.md5 is defined and iface_config.authentication.md5 is not none %} +key chain {{ iface }}-rip +{% for key_id, key_options in iface_config.authentication.md5.items() %} + key {{ key_id }} +{% if key_options.password is defined and key_options.password is not none %} + key-string {{ key_options.password }} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +! +{# Interface specific configuration #} +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} +{% if iface_config.authentication is defined and iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %} + ip rip authentication mode text + ip rip authentication string {{ iface_config.authentication.plaintext_password }} +{% elif iface_config.authentication is defined and iface_config.authentication.md5 is defined and iface_config.authentication.md5 is not none %} + ip rip authentication key-chain {{ iface }}-rip + ip rip authentication mode md5 +{% endif %} +{% if iface_config.split_horizon is defined and iface_config.split_horizon.disable is defined %} + no ip rip split-horizon +{% endif %} +{% if iface_config.split_horizon is defined and iface_config.split_horizon.poison_reverse is defined %} + ip rip split-horizon poisoned-reverse +{% endif %} +{% endfor %} +{% endif %} +! +router rip +{% if default_distance is defined and default_distance is not none %} + distance {{ default_distance }} +{% endif %} +{% if network_distance is defined and network_distance is not none %} +{% for network, network_config in network_distance.items() %} +{% if network_config.distance is defined and network_config.distance is not none %} + distance {{ network_config.distance }} {{ network }} +{% endif %} +{% endfor %} +{% endif %} +{% if neighbor is defined and neighbor is not none %} +{% for address in neighbor %} + neighbor {{ address }} +{% endfor %} +{% endif %} +{% if distribute_list is defined and distribute_list is not none %} +{% if distribute_list.access_list is defined and distribute_list.access_list is not none %} +{% if distribute_list.access_list.in is defined and distribute_list.access_list.in is not none %} + distribute-list {{ distribute_list.access_list.in }} in +{% endif %} +{% if distribute_list.access_list.out is defined and distribute_list.access_list.out is not none %} + distribute-list {{ distribute_list.access_list.out }} out +{% endif %} +{% endif %} +{% if distribute_list.interface is defined and distribute_list.interface is not none %} +{% for interface, interface_config in distribute_list.interface.items() %} +{% if interface_config.access_list is defined and interface_config.access_list is not none %} +{% if interface_config.access_list.in is defined and interface_config.access_list.in is not none %} + distribute-list {{ interface_config.access_list.in }} in {{ interface }} +{% endif %} +{% if interface_config.access_list.out is defined and interface_config.access_list.out is not none %} + distribute-list {{ interface_config.access_list.out }} out {{ interface }} +{% endif %} +{% endif %} +{% if interface_config.prefix_list is defined and interface_config.prefix_list is not none %} +{% if interface_config.prefix_list.in is defined and interface_config.prefix_list.in is not none %} + distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }} +{% endif %} +{% if interface_config.prefix_list.out is defined and interface_config.prefix_list.out is not none %} + distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{% if distribute_list.prefix_list is defined and distribute_list.prefix_list is not none %} +{% if distribute_list.prefix_list.in is defined and distribute_list.prefix_list.in is not none %} + distribute-list prefix {{ distribute_list.prefix_list.in }} in +{% endif %} +{% if distribute_list.prefix_list.out is defined and distribute_list.prefix_list.out is not none %} + distribute-list prefix {{ distribute_list.prefix_list.out }} out +{% endif %} +{% endif %} +{% endif %} +{% include 'frr/rip_ripng.frr.j2' %} +! +{% if route_map is defined and route_map is not none %} +ip protocol rip route-map {{ route_map }} +{% endif %} +! diff --git a/data/templates/frr/ripng.frr.tmpl b/data/templates/frr/ripng.frr.tmpl deleted file mode 100644 index 25df15121..000000000 --- a/data/templates/frr/ripng.frr.tmpl +++ /dev/null @@ -1,60 +0,0 @@ -! -{# Interface specific configuration #} -{% if interface is defined and interface is not none %} -{% for iface, iface_config in interface.items() %} -interface {{ iface }} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.disable is defined %} - no ipv6 rip split-horizon -{% endif %} -{% if iface_config.split_horizon is defined and iface_config.split_horizon.poison_reverse is defined %} - ipv6 rip split-horizon poisoned-reverse -{% endif %} -{% endfor %} -{% endif %} -! -router ripng -{% if aggregate_address is defined and aggregate_address is not none %} -{% for prefix in aggregate_address %} - aggregate-address {{ prefix }} -{% endfor %} -{% endif %} -{% if distribute_list is defined and distribute_list is not none %} -{% if distribute_list.access_list is defined and distribute_list.access_list is not none %} -{% if distribute_list.access_list.in is defined and distribute_list.access_list.in is not none %} - ipv6 distribute-list {{ distribute_list.access_list.in }} in -{% endif %} -{% if distribute_list.access_list.out is defined and distribute_list.access_list.out is not none %} - ipv6 distribute-list {{ distribute_list.access_list.out }} out -{% endif %} -{% endif %} -{% if distribute_list.interface is defined and distribute_list.interface is not none %} -{% for interface, interface_config in distribute_list.interface.items() %} -{% if interface_config.access_list is defined and interface_config.access_list is not none %} -{% if interface_config.access_list.in is defined and interface_config.access_list.in is not none %} - ipv6 distribute-list {{ interface_config.access_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.access_list.out is defined and interface_config.access_list.out is not none %} - ipv6 distribute-list {{ interface_config.access_list.out }} out {{ interface }} -{% endif %} -{% endif %} -{% if interface_config.prefix_list is defined and interface_config.prefix_list is not none %} -{% if interface_config.prefix_list.in is defined and interface_config.prefix_list.in is not none %} - ipv6 distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }} -{% endif %} -{% if interface_config.prefix_list.out is defined and interface_config.prefix_list.out is not none %} - ipv6 distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }} -{% endif %} -{% endif %} -{% endfor %} -{% endif %} -{% if distribute_list.prefix_list is defined and distribute_list.prefix_list is not none %} -{% if distribute_list.prefix_list.in is defined and distribute_list.prefix_list.in is not none %} - ipv6 distribute-list prefix {{ distribute_list.prefix_list.in }} in -{% endif %} -{% if distribute_list.prefix_list.out is defined and distribute_list.prefix_list.out is not none %} - ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out -{% endif %} -{% endif %} -{% endif %} -{% include 'frr/rip_ripng.frr.j2' %} -! diff --git a/data/templates/frr/ripngd.frr.tmpl b/data/templates/frr/ripngd.frr.tmpl new file mode 100644 index 000000000..25df15121 --- /dev/null +++ b/data/templates/frr/ripngd.frr.tmpl @@ -0,0 +1,60 @@ +! +{# Interface specific configuration #} +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} +{% if iface_config.split_horizon is defined and iface_config.split_horizon.disable is defined %} + no ipv6 rip split-horizon +{% endif %} +{% if iface_config.split_horizon is defined and iface_config.split_horizon.poison_reverse is defined %} + ipv6 rip split-horizon poisoned-reverse +{% endif %} +{% endfor %} +{% endif %} +! +router ripng +{% if aggregate_address is defined and aggregate_address is not none %} +{% for prefix in aggregate_address %} + aggregate-address {{ prefix }} +{% endfor %} +{% endif %} +{% if distribute_list is defined and distribute_list is not none %} +{% if distribute_list.access_list is defined and distribute_list.access_list is not none %} +{% if distribute_list.access_list.in is defined and distribute_list.access_list.in is not none %} + ipv6 distribute-list {{ distribute_list.access_list.in }} in +{% endif %} +{% if distribute_list.access_list.out is defined and distribute_list.access_list.out is not none %} + ipv6 distribute-list {{ distribute_list.access_list.out }} out +{% endif %} +{% endif %} +{% if distribute_list.interface is defined and distribute_list.interface is not none %} +{% for interface, interface_config in distribute_list.interface.items() %} +{% if interface_config.access_list is defined and interface_config.access_list is not none %} +{% if interface_config.access_list.in is defined and interface_config.access_list.in is not none %} + ipv6 distribute-list {{ interface_config.access_list.in }} in {{ interface }} +{% endif %} +{% if interface_config.access_list.out is defined and interface_config.access_list.out is not none %} + ipv6 distribute-list {{ interface_config.access_list.out }} out {{ interface }} +{% endif %} +{% endif %} +{% if interface_config.prefix_list is defined and interface_config.prefix_list is not none %} +{% if interface_config.prefix_list.in is defined and interface_config.prefix_list.in is not none %} + ipv6 distribute-list prefix {{ interface_config.prefix_list.in }} in {{ interface }} +{% endif %} +{% if interface_config.prefix_list.out is defined and interface_config.prefix_list.out is not none %} + ipv6 distribute-list prefix {{ interface_config.prefix_list.out }} out {{ interface }} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{% if distribute_list.prefix_list is defined and distribute_list.prefix_list is not none %} +{% if distribute_list.prefix_list.in is defined and distribute_list.prefix_list.in is not none %} + ipv6 distribute-list prefix {{ distribute_list.prefix_list.in }} in +{% endif %} +{% if distribute_list.prefix_list.out is defined and distribute_list.prefix_list.out is not none %} + ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out +{% endif %} +{% endif %} +{% endif %} +{% include 'frr/rip_ripng.frr.j2' %} +! diff --git a/data/templates/frr/static.frr.tmpl b/data/templates/frr/static.frr.tmpl deleted file mode 100644 index db59a44c2..000000000 --- a/data/templates/frr/static.frr.tmpl +++ /dev/null @@ -1,49 +0,0 @@ -{% from 'frr/static_routes_macro.j2' import static_routes %} -! -{% set ip_prefix = 'ip' %} -{% set ipv6_prefix = 'ipv6' %} -{% if vrf is defined and vrf is not none %} -{# We need to add an additional whitespace in front of the prefix #} -{# when VRFs are in use, thus we use a variable for prefix handling #} -{% set ip_prefix = ' ip' %} -{% set ipv6_prefix = ' ipv6' %} -vrf {{ vrf }} -{% endif %} -{# IPv4 routing #} -{% if route is defined and route is not none %} -{% for prefix, prefix_config in route.items() %} -{{ static_routes(ip_prefix, prefix, prefix_config) }} -{%- endfor -%} -{% endif %} -{# IPv6 routing #} -{% if route6 is defined and route6 is not none %} -{% for prefix, prefix_config in route6.items() %} -{{ static_routes(ipv6_prefix, prefix, prefix_config) }} -{%- endfor -%} -{% endif %} -{% if vrf is defined and vrf is not none %} - exit-vrf -{% endif %} -! -{# Policy route tables #} -{% if table is defined and table is not none %} -{% for table_id, table_config in table.items() %} -{% if table_config.route is defined and table_config.route is not none %} -{% for prefix, prefix_config in table_config.route.items() %} -{{ static_routes('ip', prefix, prefix_config, table_id) }} -{%- endfor -%} -{% endif %} -! -{% if table_config.route6 is defined and table_config.route6 is not none %} -{% for prefix, prefix_config in table_config.route6.items() %} -{{ static_routes('ipv6', prefix, prefix_config, table_id) }} -{%- endfor -%} -{% endif %} -! -{% endfor %} -{% endif %} -! -{% if route_map is defined and route_map is not none %} -ip protocol static route-map {{ route_map }} -! -{% endif %} diff --git a/data/templates/frr/staticd.frr.tmpl b/data/templates/frr/staticd.frr.tmpl new file mode 100644 index 000000000..db59a44c2 --- /dev/null +++ b/data/templates/frr/staticd.frr.tmpl @@ -0,0 +1,49 @@ +{% from 'frr/static_routes_macro.j2' import static_routes %} +! +{% set ip_prefix = 'ip' %} +{% set ipv6_prefix = 'ipv6' %} +{% if vrf is defined and vrf is not none %} +{# We need to add an additional whitespace in front of the prefix #} +{# when VRFs are in use, thus we use a variable for prefix handling #} +{% set ip_prefix = ' ip' %} +{% set ipv6_prefix = ' ipv6' %} +vrf {{ vrf }} +{% endif %} +{# IPv4 routing #} +{% if route is defined and route is not none %} +{% for prefix, prefix_config in route.items() %} +{{ static_routes(ip_prefix, prefix, prefix_config) }} +{%- endfor -%} +{% endif %} +{# IPv6 routing #} +{% if route6 is defined and route6 is not none %} +{% for prefix, prefix_config in route6.items() %} +{{ static_routes(ipv6_prefix, prefix, prefix_config) }} +{%- endfor -%} +{% endif %} +{% if vrf is defined and vrf is not none %} + exit-vrf +{% endif %} +! +{# Policy route tables #} +{% if table is defined and table is not none %} +{% for table_id, table_config in table.items() %} +{% if table_config.route is defined and table_config.route is not none %} +{% for prefix, prefix_config in table_config.route.items() %} +{{ static_routes('ip', prefix, prefix_config, table_id) }} +{%- endfor -%} +{% endif %} +! +{% if table_config.route6 is defined and table_config.route6 is not none %} +{% for prefix, prefix_config in table_config.route6.items() %} +{{ static_routes('ipv6', prefix, prefix_config, table_id) }} +{%- endfor -%} +{% endif %} +! +{% endfor %} +{% endif %} +! +{% if route_map is defined and route_map is not none %} +ip protocol static route-map {{ route_map }} +! +{% endif %} diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 348bae59f..539fd7b8e 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -92,7 +92,7 @@ def generate(bfd): bfd['new_frr_config'] = '' return None - bfd['new_frr_config'] = render_to_string('frr/bfd.frr.tmpl', bfd) + bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.tmpl', bfd) def apply(bfd): # Save original configuration prior to starting any commit actions diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index e56eb1f56..6b78f6f2d 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -93,7 +93,7 @@ def generate(rip): rip['new_frr_config'] = '' return None - rip['new_frr_config'] = render_to_string('frr/rip.frr.tmpl', rip) + rip['new_frr_config'] = render_to_string('frr/ripd.frr.tmpl', rip) return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index aaec5dacb..bc4954f63 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -95,7 +95,7 @@ def generate(ripng): ripng['new_frr_config'] = '' return None - ripng['new_frr_config'] = render_to_string('frr/ripng.frr.tmpl', ripng) + ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.tmpl', ripng) return None def apply(ripng): diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 338247e30..597fcc443 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -80,7 +80,7 @@ def verify(static): return None def generate(static): - static['new_frr_config'] = render_to_string('frr/static.frr.tmpl', static) + static['new_frr_config'] = render_to_string('frr/staticd.frr.tmpl', static) return None def apply(static): -- cgit v1.2.3 From 17dc7cd0aaca5c4ae14d3dc843de7a5b612ab5ed Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 27 Sep 2021 20:53:57 +0200 Subject: nat66: T3863: ndppd requires interfaces to be present --- src/conf_mode/nat66.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index f8bc073bb..fb376a434 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -117,7 +117,7 @@ def verify(nat): raise ConfigError(f'{err_msg} outbound-interface not specified') if config['outbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + raise ConfigError(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') addr = dict_search('translation.address', config) if addr != None: -- cgit v1.2.3 From 3600f42ba23fc42de5f13b9d79c24f6fa0043187 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 2 Oct 2021 12:02:57 +0200 Subject: dns: forwarding: T3882: remove deprecated code to work with PowerDNS 4.5 (cherry picked from commit 8e6c48563d1612916bd7fcc665d70bfa77ec5667) --- data/templates/dns-forwarding/recursor.conf.tmpl | 3 +-- src/conf_mode/dns_forwarding.py | 15 --------------- 2 files changed, 1 insertion(+), 17 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index 9e0ad5d17..d44f756e8 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -10,8 +10,7 @@ threads=1 allow-from={{ allow_from | join(',') }} log-common-errors=yes non-local-bind=yes -query-local-address={{ source_address_v4 | join(',') }} -query-local-address6={{ source_address_v6 | join(',') }} +query-local-address={{ source_address | join(',') }} lua-config-file=recursor.conf.lua # cache-size diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index c44e6c974..06366362a 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -66,21 +66,6 @@ def get_config(config=None): if conf.exists(base_nameservers_dhcp): dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) - # Split the source_address property into separate IPv4 and IPv6 lists - # NOTE: In future versions of pdns-recursor (> 4.4.0), this logic can be removed - # as both IPv4 and IPv6 addresses can be specified in a single setting. - source_address_v4 = [] - source_address_v6 = [] - - for source_address in dns['source_address']: - if is_ipv6(source_address): - source_address_v6.append(source_address) - else: - source_address_v4.append(source_address) - - dns.update({'source_address_v4': source_address_v4}) - dns.update({'source_address_v6': source_address_v6}) - return dns def verify(dns): -- cgit v1.2.3