diff options
Diffstat (limited to 'src/conf_mode')
38 files changed, 258 insertions, 262 deletions
diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py index aac07bd80..51a08bee5 100755 --- a/src/conf_mode/arp.py +++ b/src/conf_mode/arp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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,92 +13,54 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# -import sys -import os -import re -import syslog as sl +from sys import exit from vyos.config import Config +from vyos.configdict import node_changed from vyos.util import call from vyos import ConfigError - from vyos import airbag airbag.enable() -arp_cmd = '/usr/sbin/arp' - -def get_config(): - c = Config() - if not c.exists('protocols static arp'): - return None - - c.set_level('protocols static') - config_data = {} - - for ip_addr in c.list_nodes('arp'): - config_data.update( - { - ip_addr : c.return_value('arp ' + ip_addr + ' hwaddr') - } - ) - - return config_data +def get_config(config=None): + if config: + conf = config + else: + conf = Config() -def generate(c): - c_eff = Config() - c_eff.set_level('protocols static') - c_eff_cnf = {} - for ip_addr in c_eff.list_effective_nodes('arp'): - c_eff_cnf.update( - { - ip_addr : c_eff.return_effective_value('arp ' + ip_addr + ' hwaddr') - } - ) + base = ['protocols', 'static', 'arp'] + arp = conf.get_config_dict(base) + tmp = node_changed(conf, base) + if tmp: arp.update({'removed' : node_changed(conf, base)}) - config_data = { - 'remove' : [], - 'update' : {} - } - ### removal - if c == None: - for ip_addr in c_eff_cnf: - config_data['remove'].append(ip_addr) - else: - for ip_addr in c_eff_cnf: - if not ip_addr in c or c[ip_addr] == None: - config_data['remove'].append(ip_addr) + return arp - ### add/update - if c != None: - for ip_addr in c: - if not ip_addr in c_eff_cnf: - config_data['update'][ip_addr] = c[ip_addr] - if ip_addr in c_eff_cnf: - if c[ip_addr] != c_eff_cnf[ip_addr] and c[ip_addr] != None: - config_data['update'][ip_addr] = c[ip_addr] +def verify(arp): + pass - return config_data +def generate(arp): + pass -def apply(c): - for ip_addr in c['remove']: - sl.syslog(sl.LOG_NOTICE, "arp -d " + ip_addr) - call(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1') +def apply(arp): + if not arp: + return None - for ip_addr in c['update']: - sl.syslog(sl.LOG_NOTICE, "arp -s " + ip_addr + " " + c['update'][ip_addr]) - updated = c['update'][ip_addr] - call(f'{arp_cmd} -s {ip_addr} {updated}') + if 'removed' in arp: + for host in arp['removed']: + call(f'arp --delete {host}') + if 'arp' in arp: + for host, host_config in arp['arp'].items(): + mac = host_config['hwaddr'] + call(f'arp --set {host} {mac}') if __name__ == '__main__': - try: - c = get_config() - ## syntax verification is done via cli - config = generate(c) - apply(config) - except ConfigError as e: - print(e) - sys.exit(1) + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 516671844..3c1a61f19 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -28,7 +28,6 @@ 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 inc_ip from vyos.template import is_ipv4 @@ -77,10 +76,10 @@ def get_config(config=None): container['name'][name] = dict_merge(default_values, container['name'][name]) # Delete container network, delete containers - tmp = node_changed(conf, ['container', 'network']) + tmp = node_changed(conf, base + ['container', 'network']) if tmp: container.update({'network_remove' : tmp}) - tmp = node_changed(conf, ['container', 'name']) + tmp = node_changed(conf, base + ['container', 'name']) if tmp: container.update({'container_remove' : tmp}) return container diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index 6352e0b4a..4de2ca2f3 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -66,18 +66,19 @@ def generate(relay): if not relay: return None - render(config_file, 'dhcp-relay/dhcrelay.conf.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay) return None def apply(relay): # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay.service' if not relay: - call('systemctl stop isc-dhcp-relay.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) return None - call('systemctl restart isc-dhcp-relay.service') + call(f'systemctl restart {service_name}') return None diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index d27f8d995..52b682d6d 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -286,7 +286,7 @@ def generate(dhcp): # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw # parameters we can pass to ISC DHCPd tmp_file = '/tmp/dhcpd.conf' - render(tmp_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp, + render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) # XXX: as we have the ability for a user to pass in "raw" options via VyOS # CLI (see T3544) we now ask ISC dhcpd to test the newly rendered @@ -299,7 +299,7 @@ def generate(dhcp): # Now that we know that the newly rendered configuration is "good" we can # render the "real" configuration - render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp, + render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) return None diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index aea2c3b73..c1bd51f62 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -82,19 +82,20 @@ def generate(relay): if not relay: return None - render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay) return None def apply(relay): # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay6.service' if not relay: # DHCPv6 relay support is removed in the commit - call('systemctl stop isc-dhcp-relay6.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) return None - call('systemctl restart isc-dhcp-relay6.service') + call(f'systemctl restart {service_name}') return None diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index be1e6db1e..078ff327c 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -41,7 +41,9 @@ def get_config(config=None): if not conf.exists(base): return None - dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) return dhcpv6 def verify(dhcpv6): @@ -51,7 +53,7 @@ def verify(dhcpv6): # If DHCP is enabled we need one share-network if 'shared_network_name' not in dhcpv6: - raise ConfigError('No DHCPv6 shared networks configured. At least\n' \ + raise ConfigError('No DHCPv6 shared networks configured. At least '\ 'one DHCPv6 shared network must be configured.') # Inspect shared-network/subnet @@ -60,8 +62,9 @@ def verify(dhcpv6): for network, network_config in dhcpv6['shared_network_name'].items(): # A shared-network requires a subnet definition if 'subnet' not in network_config: - raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". At least one\n' \ - 'lease subnet must be configured for each shared network!') + raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\ + 'At least one lease subnet must be configured for '\ + 'each shared network!') for subnet, subnet_config in network_config['subnet'].items(): if 'address_range' in subnet_config: @@ -83,20 +86,20 @@ def verify(dhcpv6): # Stop address must be greater or equal to start address if not ip_address(stop) >= ip_address(start): - raise ConfigError(f'address-range stop address "{stop}" must be greater or equal\n' \ + raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \ f'to the range start address "{start}"!') # DHCPv6 range start address must be unique - two ranges can't # start with the same address - makes no sense if start in range6_start: - raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \ + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ f'Pool start address "{start}" defined multipe times!') range6_start.append(start) # DHCPv6 range stop address must be unique - two ranges can't # end with the same address - makes no sense if stop in range6_stop: - raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \ + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ f'Pool stop address "{stop}" defined multipe times!') range6_stop.append(stop) @@ -112,7 +115,7 @@ def verify(dhcpv6): for prefix, prefix_config in subnet_config['prefix_delegation']['start'].items(): if 'stop' not in prefix_config: - raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}"\n' + raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}" '\ f'must be configured') if 'prefix_length' not in prefix_config: @@ -126,6 +129,10 @@ def verify(dhcpv6): if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet): raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!') + if 'vendor_option' in subnet_config: + if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2: + raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!') + # Subnets must be unique if subnet in subnets: raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!') @@ -149,8 +156,8 @@ def verify(dhcpv6): raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2)) if not listen_ok: - raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on\n' \ - 'this machine. At least one subnet6 must be connected such that\n' \ + raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\ + 'this machine. At least one subnet6 must be connected such that '\ 'DHCPv6 listens on an interface!') @@ -161,20 +168,20 @@ def generate(dhcpv6): if not dhcpv6 or 'disable' in dhcpv6: return None - render(config_file, 'dhcp-server/dhcpdv6.conf.tmpl', dhcpv6) + render(config_file, 'dhcp-server/dhcpdv6.conf.j2', dhcpv6) return None def apply(dhcpv6): # bail out early - looks like removal from running config + service_name = 'isc-dhcp-server6.service' if not dhcpv6 or 'disable' in dhcpv6: # DHCP server is removed in the commit - call('systemctl stop isc-dhcp-server6.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) - return None - call('systemctl restart isc-dhcp-server6.service') + call(f'systemctl restart {service_name}') return None if __name__ == '__main__': diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index fa9b21f20..f1c2d1f43 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -279,10 +279,10 @@ def generate(dns): if not dns: return None - render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', + render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2', dns, user=pdns_rec_user, group=pdns_rec_group) - render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', + render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2', dns, user=pdns_rec_user, group=pdns_rec_group) for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): @@ -290,7 +290,7 @@ def generate(dns): if 'authoritative_zones' in dns: for zone in dns['authoritative_zones']: - render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl', + render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2', zone, user=pdns_rec_user, group=pdns_rec_group) diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index a31e5ed75..06a2f7e15 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -131,7 +131,7 @@ def generate(dyndns): if not dyndns: return None - render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns) + render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns) return None def apply(dyndns): diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index f33198a49..de78d53a8 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -21,6 +21,7 @@ from glob import glob from json import loads from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed @@ -225,7 +226,7 @@ def verify_rule(firewall, rule_conf, ipv6): raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') if not group_obj: - print(f'WARNING: {error_group} "{group_name}" has no members') + Warning(f'{error_group} "{group_name}" has no members!') if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): if 'protocol' not in rule_conf: @@ -395,7 +396,7 @@ def resync_policy_route(): # Update policy route as firewall groups were updated tmp = run(policy_route_conf_script) if tmp > 0: - print('Warning: Failed to re-apply policy route configuration') + Warning('Failed to re-apply policy route configuration!') def apply(firewall): if 'first_install' in firewall: diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 87bad0dc6..93f244f42 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -21,13 +21,14 @@ import copy import vyos.util import vyos.hostsd_client -from vyos import ConfigError +from vyos.base import Warning 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 ConfigError from vyos import airbag airbag.enable() @@ -113,7 +114,7 @@ def verify(hosts): 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!') + Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!') return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index fb030c9f3..37df3dc92 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -19,6 +19,7 @@ import os from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render @@ -92,7 +93,7 @@ def generate(igmp_proxy): # bail out early - service is disabled, but inform user if 'disable' in igmp_proxy: - print('WARNING: IGMP Proxy will be deactivated because it is disabled') + Warning('IGMP Proxy will be deactivated because it is disabled') return None render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index ad5a0f499..4167594e3 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -68,7 +68,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'bonding'] - bond = get_interface_dict(conf, base) + ifname, bond = get_interface_dict(conf, base) # To make our own life easier transfor the list of member interfaces # into a dictionary - we will use this to add additional information @@ -81,14 +81,14 @@ def get_config(config=None): if 'mode' in bond: bond['mode'] = get_bond_mode(bond['mode']) - tmp = leaf_node_changed(conf, ['mode']) + tmp = leaf_node_changed(conf, base + [ifname, 'mode']) if tmp: bond.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['lacp-rate']) + tmp = leaf_node_changed(conf, base + [ifname, 'lacp-rate']) if tmp: bond.update({'shutdown_required': {}}) # determine which members have been removed - interfaces_removed = leaf_node_changed(conf, ['member', 'interface']) + interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) if interfaces_removed: bond.update({'shutdown_required': {}}) if 'member' not in bond: diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index b1f7e6d7c..38ae727c1 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -50,15 +50,15 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'bridge'] - bridge = get_interface_dict(conf, base) + ifname, bridge = get_interface_dict(conf, base) # determine which members have been removed - tmp = node_changed(conf, ['member', 'interface'], key_mangling=('-', '_')) + tmp = node_changed(conf, base + [ifname, 'member', 'interface'], key_mangling=('-', '_')) if tmp: if 'member' in bridge: - bridge['member'].update({'interface_remove': tmp }) + bridge['member'].update({'interface_remove' : tmp }) else: - bridge.update({'member': {'interface_remove': tmp }}) + bridge.update({'member' : {'interface_remove' : tmp }}) if dict_search('member.interface', bridge): # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 4a1eb7b93..e771581e1 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -37,7 +37,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'dummy'] - dummy = get_interface_dict(conf, base) + _, dummy = get_interface_dict(conf, base) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 68f59893a..fec4456fb 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -19,6 +19,7 @@ import os from glob import glob from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_address @@ -64,7 +65,7 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) base = ['interfaces', 'ethernet'] - ethernet = get_interface_dict(conf, base) + _, ethernet = get_interface_dict(conf, base) if 'deleted' not in ethernet: if pki: ethernet['pki'] = pki @@ -142,8 +143,8 @@ def verify(ethernet): raise ConfigError('XDP requires additional TX queues, too few available!') if {'is_bond_member', 'mac'} <= set(ethernet): - print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" ' - f'is a member of bond "{is_bond_member}"'.format(**ethernet)) + Warning(f'changing mac address "{mac}" will be ignored as "{ifname}" ' \ + f'is a member of bond "{is_bond_member}"'.format(**ethernet)) # use common function to verify VLAN configuration verify_vlan_config(ethernet) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 3a668226b..b9cf2fa3c 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -21,6 +21,8 @@ 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.configdict import is_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete @@ -41,7 +43,18 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'geneve'] - geneve = get_interface_dict(conf, base) + ifname, geneve = get_interface_dict(conf, base) + + # GENEVE interfaces are picky and require recreation if certain parameters + # change. But a GENEVE interface should - of course - not be re-created if + # it's description or IP address is adjusted. Feels somehow logic doesn't it? + for cli_option in ['remote', 'vni']: + if leaf_node_changed(conf, base + [ifname, cli_option]): + geneve.update({'rebuild_required': {}}) + + if is_node_changed(conf, base + [ifname, 'parameters']): + geneve.update({'rebuild_required': {}}) + return geneve def verify(geneve): @@ -67,11 +80,12 @@ def generate(geneve): def apply(geneve): # Check if GENEVE interface already exists - if geneve['ifname'] in interfaces(): - g = GeneveIf(geneve['ifname']) - # GENEVE is super picky and the tunnel always needs to be recreated, - # thus we can simply always delete it first. - g.remove() + if 'rebuild_required' in geneve or 'delete' in geneve: + if geneve['ifname'] in interfaces(): + g = GeneveIf(geneve['ifname']) + # GENEVE is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + g.remove() if 'deleted' not in geneve: # Finally create the new interface diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 22256bf4f..6a486f969 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -45,15 +45,15 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'l2tpv3'] - l2tpv3 = get_interface_dict(conf, base) + ifname, l2tpv3 = get_interface_dict(conf, base) # To delete an l2tpv3 interface we need the current tunnel and session-id if 'deleted' in l2tpv3: - tmp = leaf_node_changed(conf, ['tunnel-id']) + tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id']) # leaf_node_changed() returns a list l2tpv3.update({'tunnel_id': tmp[0]}) - tmp = leaf_node_changed(conf, ['session-id']) + tmp = leaf_node_changed(conf, base + [ifname, 'session-id']) l2tpv3.update({'session_id': tmp[0]}) return l2tpv3 diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index e4bc15bb5..08d34477a 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -36,7 +36,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'loopback'] - loopback = get_interface_dict(conf, base) + _, loopback = get_interface_dict(conf, base) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index c71863e61..279dd119b 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -48,7 +48,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'macsec'] - macsec = get_interface_dict(conf, base) + ifname, macsec = get_interface_dict(conf, base) # Check if interface has been removed if 'deleted' in macsec: diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 83d1c6d9b..4750ca3e8 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -32,7 +32,7 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mirror_redirect @@ -85,13 +85,12 @@ def get_config(config=None): tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - openvpn = get_interface_dict(conf, base) + ifname, openvpn = get_interface_dict(conf, base) if 'deleted' not in openvpn: openvpn['pki'] = tmp_pki - - tmp = leaf_node_changed(conf, ['openvpn-option']) - if tmp: openvpn['restart_required'] = '' + if is_node_changed(conf, base + [ifname, 'openvpn-option']): + openvpn.update({'restart_required': {}}) # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. @@ -608,7 +607,7 @@ def generate(openvpn): # Generate User/Password authentication file if 'authentication' in openvpn: - render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn, + render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn, user=user, group=group, permission=0o600) else: # delete old auth file if present @@ -624,16 +623,16 @@ def generate(openvpn): # Our client need's to know its subnet mask ... client_config['server_subnet'] = dict_search('server.subnet', openvpn) - render(client_file, 'openvpn/client.conf.tmpl', client_config, + render(client_file, 'openvpn/client.conf.j2', client_config, user=user, group=group) # we need to support quoting of raw parameters from OpenVPN CLI # see https://phabricator.vyos.net/T1632 - render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn, + render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn, formater=lambda _: _.replace(""", '"'), user=user, group=group) # Render 20-override.conf for OpenVPN service - render(service_file.format(**openvpn), 'openvpn/service-override.conf.tmpl', openvpn, + render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn, formater=lambda _: _.replace(""", '"'), user=user, group=group) # Reload systemd services config to apply an override call(f'systemctl daemon-reload') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index bfb1fadd5..26daa8381 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -22,7 +22,9 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed from vyos.configdict import leaf_node_changed +from vyos.configdict import get_pppoe_interfaces from vyos.configverify import verify_authentication from vyos.configverify import verify_source_interface from vyos.configverify import verify_interface_exists @@ -47,33 +49,17 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'pppoe'] - pppoe = get_interface_dict(conf, base) + ifname, 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': {}}) + for options in ['access-concentrator', 'connect-on-demand', 'service-name', + 'source-interface', 'vrf', 'no-default-route', 'authentication']: + if is_node_changed(conf, base + [ifname, options]): + pppoe.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break return pppoe @@ -120,7 +106,7 @@ def apply(pppoe): return None # reconnect should only be necessary when certain config options change, - # like ACS name, authentication, no-peer-dns, source-interface + # like ACS name, authentication ... (see get_config() for details) if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or 'shutdown_required' in pppoe): @@ -130,6 +116,9 @@ def apply(pppoe): p.remove() call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/99-vyos-pppoe-callback + # which triggers PPPoEIf.update() else: if os.path.isdir(f'/sys/class/net/{ifname}'): p = PPPoEIf(ifname) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f2c85554f..1cd3fe276 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -18,7 +18,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -42,14 +42,14 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'pseudo-ethernet'] - peth = get_interface_dict(conf, base) + ifname, peth = get_interface_dict(conf, base) - mode = leaf_node_changed(conf, ['mode']) - if mode: peth.update({'mode_old' : mode}) + mode = is_node_changed(conf, ['mode']) + if mode: peth.update({'shutdown_required' : {}}) if 'source_interface' in peth: - peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], - peth['source_interface']) + _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], + peth['source_interface']) return peth def verify(peth): diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index f4668d976..eff7f373c 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -48,10 +48,10 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'tunnel'] - tunnel = get_interface_dict(conf, base) + ifname, tunnel = get_interface_dict(conf, base) if 'deleted' not in tunnel: - tmp = leaf_node_changed(conf, ['encapsulation']) + tmp = leaf_node_changed(conf, base + [ifname, 'encapsulation']) if tmp: tunnel.update({'encapsulation_changed': {}}) # We also need to inspect other configured tunnels as there are Kernel diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index f06fdff1b..f4b0436af 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -36,7 +36,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'vti'] - vti = get_interface_dict(conf, base) + _, vti = get_interface_dict(conf, base) return vti def verify(vti): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 0a9b51cac..f44d754ba 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -19,9 +19,11 @@ import os from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 @@ -44,18 +46,19 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'vxlan'] - vxlan = get_interface_dict(conf, base) + ifname, vxlan = get_interface_dict(conf, base) # VXLAN interfaces are picky and require recreation if certain parameters # change. But a VXLAN interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? for cli_option in ['external', 'gpe', 'group', 'port', 'remote', - 'source-address', 'source-interface', 'vni', - 'parameters ip dont-fragment', 'parameters ip tos', - 'parameters ip ttl']: - if leaf_node_changed(conf, cli_option.split()): + 'source-address', 'source-interface', 'vni']: + if leaf_node_changed(conf, base + [ifname, cli_option]): vxlan.update({'rebuild_required': {}}) + if is_node_changed(conf, base + [ifname, 'parameters']): + vxlan.update({'rebuild_required': {}}) + # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation conf.set_level(base) @@ -78,7 +81,7 @@ def verify(vxlan): return None if int(vxlan['mtu']) < 1500: - print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') + Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') if 'group' in vxlan: if 'source_interface' not in vxlan: diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index b404375d6..180ffa507 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -46,17 +46,17 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'wireguard'] - wireguard = get_interface_dict(conf, base) + ifname, wireguard = get_interface_dict(conf, base) # Check if a port was changed - wireguard['port_changed'] = leaf_node_changed(conf, ['port']) + wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port']) # Determine which Wireguard peer has been removed. # Peers can only be removed with their public key! dict = {} - tmp = node_changed(conf, ['peer'], key_mangling=('-', '_')) + tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_')) for peer in (tmp or []): - public_key = leaf_node_changed(conf, ['peer', peer, 'public_key']) + public_key = leaf_node_changed(conf, base + [ifname, 'peer', peer, 'public_key']) if public_key: dict = dict_merge({'peer_remove' : {peer : {'public_key' : public_key[0]}}}, dict) wireguard.update(dict) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 7fc22cdab..d34297063 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -76,15 +76,19 @@ def get_config(config=None): conf = Config() base = ['interfaces', 'wireless'] - wifi = get_interface_dict(conf, base) + ifname, wifi = get_interface_dict(conf, base) # Cleanup "delete" default values when required user selectable values are # not defined at all - tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + tmp = conf.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True) if not (dict_search('security.wpa.passphrase', tmp) or dict_search('security.wpa.radius', tmp)): if 'deleted' not in wifi: del wifi['security']['wpa'] + # if 'security' key is empty, drop it too + if len(wifi['security']) == 0: + del wifi['security'] # defaults include RADIUS server specifics per TAG node which need to be # added to individual RADIUS servers instead - so we can simply delete them diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 9a33039a3..e275ace84 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -21,7 +21,7 @@ from time import sleep from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_authentication from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mirror_redirect @@ -50,42 +50,36 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'wwan'] - wwan = get_interface_dict(conf, base) + ifname, wwan = get_interface_dict(conf, base) # We should only terminate the WWAN 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, ['address']) + tmp = is_node_changed(conf, base + [ifname, 'address']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['apn']) + tmp = is_node_changed(conf, base + [ifname, 'apn']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['disable']) + tmp = is_node_changed(conf, base + [ifname, 'disable']) if tmp: wwan.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: wwan.update({'vrf_old': tmp[0]}) - - tmp = leaf_node_changed(conf, ['authentication', 'user']) + tmp = is_node_changed(conf, base + [ifname, 'vrf']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['authentication', 'password']) + tmp = is_node_changed(conf, base + [ifname, 'authentication']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['ipv6', 'address', 'autoconf']) + tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf']) if tmp: wwan.update({'shutdown_required': {}}) # We need to know the amount of other WWAN interfaces as ModemManager needs # to be started or stopped. conf.set_level(base) - wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) + _, wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - ifname = wwan['ifname'] # This if-clause is just to be sure - it will always evaluate to true if ifname in wwan['other_interfaces']: del wwan['other_interfaces'][ifname] diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index db8328259..2bb615eb7 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -18,6 +18,7 @@ import os from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.validate import is_addr_assigned @@ -84,11 +85,11 @@ def verify(lldp): if 'management_address' in lldp: for address in lldp['management_address']: - message = f'WARNING: LLDP management address "{address}" is invalid' + message = f'LLDP management address "{address}" is invalid' if is_loopback_addr(address): - print(f'{message} - loopback address') + Warning(f'{message} - loopback address') elif not is_addr_assigned(address): - print(f'{message} - not assigned to any interface') + Warning(f'{message} - not assigned to any interface') if 'interface' in lldp: for interface, interface_config in lldp['interface'].items(): diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 14ca7bc94..8aaebf9ff 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -23,6 +23,7 @@ from platform import release as kernel_version from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render @@ -142,14 +143,14 @@ def verify(nat): 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') + Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') addr = dict_search('translation.address', config) if addr != None: if addr != 'masquerade' and not is_ip_network(addr): for ip in addr.split('-'): if not is_addr_assigned(ip): - print(f'WARNING: IP address {ip} does not exist on the system!') + Warning(f'IP address {ip} does not exist on the system!') elif 'exclude' not in config: raise ConfigError(f'{err_msg}\n' \ 'translation address not specified') @@ -167,7 +168,7 @@ def verify(nat): 'inbound-interface not specified') else: if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') + Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') if dict_search('translation.address', config) == None and 'exclude' not in config: diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 8bf2e8073..1cd15811f 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -21,6 +21,7 @@ import os from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render @@ -117,12 +118,12 @@ def verify(nat): raise ConfigError(f'{err_msg} outbound-interface not specified') if config['outbound_interface'] not in interfaces(): - raise ConfigError(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + raise ConfigError(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') addr = dict_search('translation.address', config) if addr != None: if addr != 'masquerade' and not is_ipv6(addr): - raise ConfigError(f'Warning: IPv6 address {addr} is not a valid address') + raise ConfigError(f'IPv6 address {addr} is not a valid address') else: raise ConfigError(f'{err_msg} translation address not specified') @@ -140,7 +141,7 @@ def verify(nat): 'inbound-interface not specified') else: if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') + Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') return None diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 3d1d7d8c5..09d181d43 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -20,6 +20,7 @@ import re from json import loads from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.template import render from vyos.util import cmd @@ -135,7 +136,7 @@ def verify_rule(policy, name, rule_conf, ipv6): raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule') if not group_obj: - print(f'WARNING: {error_group} "{group_name}" has no members') + Warning(f'{error_group} "{group_name}" has no members') if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): if 'protocol' not in rule_conf: diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index f6d5071c2..a9173ab87 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -138,13 +138,20 @@ def verify(bgp): if asn == bgp['local_as']: raise ConfigError('Cannot have local-as same as BGP AS number') + # Neighbor AS specified for local-as and remote-as can not be the same + if dict_search('remote_as', peer_config) == asn: + raise ConfigError(f'Neighbor "{peer}" has local-as specified which is '\ + 'the same as remote-as, this is not allowed!') + # 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 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. + # 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: - raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and strict-capability-match configured at the same time!') + raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and '\ + 'strict-capability-match configured at the same time!') # Check spaces in the password if 'password' in peer_config and ' ' in peer_config['password']: @@ -157,6 +164,12 @@ def verify(bgp): if not verify_remote_as(peer_config, bgp): raise ConfigError(f'Neighbor "{peer}" remote-as must be set!') + # Peer-group member cannot override remote-as of peer-group + if 'peer_group' in peer_config: + peer_group = peer_config['peer_group'] + if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]: + raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!') + # Only checks for ipv4 and ipv6 neighbors # Check if neighbor address is assigned as system interface address vrf = None diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 87432bc1c..58e202928 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -22,6 +22,7 @@ from sys import argv from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import get_dhcp_interfaces +from vyos.configdict import get_pppoe_interfaces from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf from vyos.template import render_to_string @@ -59,7 +60,9 @@ def get_config(config=None): # T3680 - get a list of all interfaces currently configured to use DHCP tmp = get_dhcp_interfaces(conf, vrf) - if tmp: static['dhcp'] = tmp + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf, vrf) + if tmp: static.update({'pppoe' : tmp}) return static diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 841bf6a39..00b889a11 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2022 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 @@ -16,14 +16,18 @@ import os -from copy import deepcopy from socket import gethostname from sys import exit from urllib3 import PoolManager +from vyos.base import Warning from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_interface_exists from vyos.template import render -from vyos.util import call, chown +from vyos.util import call +from vyos.util import chown +from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -32,20 +36,10 @@ airbag.enable() config_file = r'/etc/salt/minion' master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub' -default_config_data = { - 'hash': 'sha256', - 'log_level': 'warning', - 'master' : 'salt', - 'user': 'minion', - 'group': 'vyattacfg', - 'salt_id': gethostname(), - 'mine_interval': '60', - 'verify_master_pubkey_sign': 'false', - 'master_key': '' -} +user='minion' +group='vyattacfg' def get_config(config=None): - salt = deepcopy(default_config_data) if config: conf = config else: @@ -54,44 +48,44 @@ def get_config(config=None): if not conf.exists(base): return None - else: - conf.set_level(base) - if conf.exists(['hash']): - salt['hash'] = conf.return_value(['hash']) + salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # ID default is dynamic thus we can not use defaults() + if 'id' not in salt: + salt['id'] = gethostname() + # 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) + salt = dict_merge(default_values, salt) - if conf.exists(['master']): - salt['master'] = conf.return_values(['master']) - - if conf.exists(['id']): - salt['salt_id'] = conf.return_value(['id']) + if not conf.exists(base): + return None + else: + conf.set_level(base) - if conf.exists(['user']): - salt['user'] = conf.return_value(['user']) + return salt - if conf.exists(['interval']): - salt['interval'] = conf.return_value(['interval']) +def verify(salt): + if not salt: + return None - if conf.exists(['master-key']): - salt['master_key'] = conf.return_value(['master-key']) - salt['verify_master_pubkey_sign'] = 'true' + if 'hash' in salt and salt['hash'] == 'sha1': + Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!') - return salt + if 'source_interface' in salt: + verify_interface_exists(salt['source_interface']) -def verify(salt): return None def generate(salt): if not salt: return None - render(config_file, 'salt-minion/minion.tmpl', salt, - user=salt['user'], group=salt['group']) + render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group) if not os.path.exists(master_keyfile): - if salt['master_key']: + if 'master_key' in salt: req = PoolManager().request('GET', salt['master_key'], preload_content=False) - with open(master_keyfile, 'wb') as f: while True: data = req.read(1024) @@ -100,18 +94,19 @@ def generate(salt): f.write(data) req.release_conn() - chown(master_keyfile, salt['user'], salt['group']) + chown(master_keyfile, user, group) return None def apply(salt): + service_name = 'salt-minion.service' if not salt: # Salt removed from running config - call('systemctl stop salt-minion.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) else: - call('systemctl restart salt-minion.service') + call(f'systemctl restart {service_name}') return None diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 4fc6a4517..e35bb8a0c 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -18,6 +18,7 @@ import os from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_vrf @@ -149,7 +150,7 @@ def verify(snmp): tmp = extension_opt['script'] if not os.path.isfile(tmp): - print(f'WARNING: script "{tmp}" does not exist!') + Warning(f'script "{tmp}" does not exist!') else: chmod_755(extension_opt['script']) @@ -158,7 +159,7 @@ def verify(snmp): # We only wan't to configure addresses that exist on the system. # Hint the user if they don't exist if not is_addr_assigned(address): - print(f'WARNING: SNMP listen address "{address}" not configured!') + Warning(f'SNMP listen address "{address}" not configured!') if 'trap_target' in snmp: for trap, trap_config in snmp['trap_target'].items(): diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index ef726670c..95050624e 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -22,6 +22,7 @@ from copy import deepcopy from glob import glob from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_vrf @@ -68,8 +69,8 @@ def verify(tftpd): for address, address_config in tftpd['listen_address'].items(): if not is_addr_assigned(address): - print(f'WARNING: TFTP server listen address "{address}" not ' \ - 'assigned to any interface!') + Warning(f'TFTP server listen address "{address}" not ' \ + 'assigned to any interface!') verify_vrf(address_config) return None diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 99b82ca2d..dc134fd1f 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -553,13 +553,15 @@ def generate(ipsec): if not local_prefixes or not remote_prefixes: continue - passthrough = [] + passthrough = None for local_prefix in local_prefixes: for remote_prefix in remote_prefixes: local_net = ipaddress.ip_network(local_prefix) remote_net = ipaddress.ip_network(remote_prefix) if local_net.overlaps(remote_net): + if passthrough is None: + passthrough = [] passthrough.append(local_prefix) ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough |