diff options
Diffstat (limited to 'src')
33 files changed, 270 insertions, 168 deletions
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index a8cef5ebf..d27f8d995 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 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 @@ -109,7 +109,7 @@ def get_config(config=None): if not conf.exists(base): return None - dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) # T2665: defaults include lease time per TAG node which need to be added to # individual subnet definitions default_values = defaults(base + ['shared-network-name', 'subnet']) diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index e6a2e4486..be1e6db1e 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.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 @@ -41,7 +41,7 @@ def get_config(config=None): if not conf.exists(base): return None - dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) return dhcpv6 def verify(dhcpv6): diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 23a16df63..fa9b21f20 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.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,6 +16,7 @@ import os +from netifaces import interfaces from sys import exit from glob import glob @@ -65,10 +66,6 @@ def get_config(config=None): if conf.exists(base_nameservers): dns.update({'system_name_server': conf.return_values(base_nameservers)}) - base_nameservers_dhcp = ['system', 'name-servers-dhcp'] - if conf.exists(base_nameservers_dhcp): - dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) - if 'authoritative_domain' in dns: dns['authoritative_zones'] = [] dns['authoritative_zone_errors'] = [] @@ -272,9 +269,8 @@ def verify(dns): raise ConfigError('Invalid authoritative records have been defined') if 'system' in dns: - if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): - print("Warning: No 'system name-server' or 'system " \ - "name-servers-dhcp' configured") + if not 'system_name_server' in dns: + print('Warning: No "system name-server" configured') return None @@ -339,10 +335,15 @@ def apply(dns): hc.delete_name_server_tags_recursor(['system']) # add dhcp nameserver tags for configured interfaces - if 'system_name_server_dhcp' in dns: - for interface in dns['system_name_server_dhcp']: - hc.add_name_server_tags_recursor(['dhcp-' + interface, - 'dhcpv6-' + interface ]) + if 'system_name_server' in dns: + for interface in dns['system_name_server']: + # system_name_server key contains both IP addresses and interface + # names (DHCP) to use DNS servers. We need to check if the + # value is an interface name - only if this is the case, add the + # interface based DNS forwarder. + if interface in interfaces(): + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) # hostsd will generate the forward-zones file # the list and keys() are required as get returns a dict, not list diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 41df1b84a..f33198a49 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -171,6 +171,12 @@ def verify_rule(firewall, rule_conf, ipv6): if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']): raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') + if 'limit' in rule_conf: + if 'rate' in rule_conf['limit']: + rate_int = re.sub(r'\D', '', rule_conf['limit']['rate']) + if int(rate_int) < 1: + raise ConfigError('Limit rate integer cannot be less than 1') + if 'ipsec' in rule_conf: if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']): raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"') diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 661dc2298..ad5a0f499 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -27,9 +27,8 @@ from vyos.configdict import is_source_interface from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_mirror +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf @@ -151,8 +150,7 @@ def verify(bond): verify_address(bond) verify_dhcpv6(bond) verify_vrf(bond) - verify_mirror(bond) - verify_redirect(bond) + verify_mirror_redirect(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index e16c0e9f4..b1f7e6d7c 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -27,8 +27,7 @@ from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_mirror -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf from vyos.validate import has_address_configured @@ -107,8 +106,7 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) - verify_mirror(bridge) - verify_redirect(bridge) + verify_mirror_redirect(bridge) ifname = bridge['ifname'] diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 4072c4452..4a1eb7b93 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -21,7 +21,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import DummyIf from vyos import ConfigError from vyos import airbag @@ -47,7 +47,7 @@ def verify(dummy): verify_vrf(dummy) verify_address(dummy) - verify_redirect(dummy) + verify_mirror_redirect(dummy) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 3eeddf190..6aea7a80e 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -25,10 +25,9 @@ from vyos.configverify import verify_address from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_eapol from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_mirror +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_mtu from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ethtool import Ethtool @@ -84,8 +83,7 @@ def verify(ethernet): verify_address(ethernet) verify_vrf(ethernet) verify_eapol(ethernet) - verify_mirror(ethernet) - verify_redirect(ethernet) + verify_mirror_redirect(ethernet) ethtool = Ethtool(ifname) # No need to check speed and duplex keys as both have default values. diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index a94b5e1f7..3a668226b 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -24,7 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import GeneveIf from vyos import ConfigError @@ -51,7 +51,7 @@ def verify(geneve): verify_mtu_ipv6(geneve) verify_address(geneve) - verify_redirect(geneve) + verify_mirror_redirect(geneve) if 'remote' not in geneve: raise ConfigError('Remote side must be configured') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 5ea7159dc..22256bf4f 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -25,7 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import L2TPv3If from vyos.util import check_kmod from vyos.validate import is_addr_assigned @@ -77,7 +77,7 @@ def verify(l2tpv3): verify_mtu_ipv6(l2tpv3) verify_address(l2tpv3) - verify_redirect(l2tpv3) + verify_mirror_redirect(l2tpv3) return None def generate(l2tpv3): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index e6a851113..e4bc15bb5 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -20,7 +20,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import LoopbackIf from vyos import ConfigError from vyos import airbag @@ -40,7 +40,7 @@ def get_config(config=None): return loopback def verify(loopback): - verify_redirect(loopback) + verify_mirror_redirect(loopback) return None def generate(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 6a29fdb11..96fc1c41c 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -29,7 +29,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_source_interface from vyos import ConfigError from vyos import airbag @@ -67,7 +67,7 @@ def verify(macsec): verify_vrf(macsec) verify_mtu_ipv6(macsec) verify_address(macsec) - verify_redirect(macsec) + verify_mirror_redirect(macsec) if not (('security' in macsec) and ('cipher' in macsec['security'])): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 8f9c0b3f1..83d1c6d9b 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -35,6 +35,7 @@ from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import VTunIf from vyos.pki import load_dh_parameters from vyos.pki import load_private_key @@ -495,6 +496,7 @@ def verify(openvpn): raise ConfigError('Username for authentication is missing') verify_vrf(openvpn) + verify_mirror_redirect(openvpn) return None diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 9962e0a08..bfb1fadd5 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -28,7 +28,7 @@ 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.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import PPPoEIf from vyos.template import render from vyos.util import call @@ -86,7 +86,7 @@ def verify(pppoe): verify_authentication(pppoe) verify_vrf(pppoe) verify_mtu_ipv6(pppoe) - verify_redirect(pppoe) + verify_mirror_redirect(pppoe) if {'connect_on_demand', 'vrf'} <= set(pppoe): raise ConfigError('On-demand dialing and VRF can not be used at the same time') diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f57e41cc4..f2c85554f 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -25,7 +25,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_mtu_parent -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import MACVLANIf from vyos import ConfigError @@ -61,7 +61,7 @@ def verify(peth): verify_vrf(peth) verify_address(peth) verify_mtu_parent(peth, peth['parent']) - verify_redirect(peth) + verify_mirror_redirect(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 005fae5eb..f4668d976 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -26,7 +26,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface @@ -158,7 +158,7 @@ def verify(tunnel): verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) - verify_redirect(tunnel) + verify_mirror_redirect(tunnel) if 'source_interface' in tunnel: verify_interface_exists(tunnel['source_interface']) diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index 30e13536f..f06fdff1b 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -19,7 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import VTIIf from vyos.util import dict_search from vyos import ConfigError @@ -40,7 +40,7 @@ def get_config(config=None): return vti def verify(vti): - verify_redirect(vti) + verify_mirror_redirect(vti) return None def generate(vti): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index a29836efd..0a9b51cac 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -25,7 +25,7 @@ from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_source_interface from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf @@ -141,7 +141,7 @@ def verify(vxlan): verify_mtu_ipv6(vxlan) verify_address(vxlan) - verify_redirect(vxlan) + verify_mirror_redirect(vxlan) return None def generate(vxlan): diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index dc0fe7b9c..b404375d6 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -28,7 +28,7 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import WireGuardIf from vyos.util import check_kmod from vyos.util import check_port_availability @@ -71,7 +71,7 @@ def verify(wireguard): verify_mtu_ipv6(wireguard) verify_address(wireguard) verify_vrf(wireguard) - verify_redirect(wireguard) + verify_mirror_redirect(wireguard) if 'private_key' not in wireguard: raise ConfigError('Wireguard private-key not defined') diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index fdf9e3988..500952df1 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -27,7 +27,7 @@ from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_source_interface -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import WiFiIf @@ -190,7 +190,7 @@ def verify(wifi): verify_address(wifi) verify_vrf(wifi) - verify_redirect(wifi) + verify_mirror_redirect(wifi) # use common function to verify VLAN configuration verify_vlan_config(wifi) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 367a50e82..9a33039a3 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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,9 +21,10 @@ 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.configverify import verify_authentication from vyos.configverify import verify_interface_exists -from vyos.configverify import verify_redirect +from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf from vyos.util import cmd @@ -37,7 +38,7 @@ from vyos import airbag airbag.enable() service_name = 'ModemManager.service' -cron_script = '/etc/cron.d/wwan' +cron_script = '/etc/cron.d/vyos-wwan' def get_config(config=None): """ @@ -51,6 +52,32 @@ def get_config(config=None): base = ['interfaces', 'wwan'] 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']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['apn']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['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']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['authentication', 'password']) + if tmp: wwan.update({'shutdown_required': {}}) + + tmp = leaf_node_changed(conf, ['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) @@ -58,8 +85,8 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) - # This if-clause is just to be sure - it will always evaluate to 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] if len(wwan['other_interfaces']) == 0: @@ -78,19 +105,31 @@ def verify(wwan): verify_interface_exists(ifname) verify_authentication(wwan) verify_vrf(wwan) - verify_redirect(wwan) + verify_mirror_redirect(wwan) return None def generate(wwan): if 'deleted' in wwan: + # We are the last WWAN interface - there are no other ones remaining + # thus the cronjob needs to go away, too + if 'other_interfaces' not in wwan: + if os.path.exists(cron_script): + os.unlink(cron_script) return None + # Install cron triggered helper script to re-dial WWAN interfaces on + # disconnect - e.g. happens during RF signal loss. The script watches every + # WWAN interface - so there is only one instance. if not os.path.exists(cron_script): write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py') + return None def apply(wwan): + # ModemManager is required to dial WWAN connections - one instance is + # required to serve all modems. Activate ModemManager on first invocation + # of any WWAN interface. if not is_systemd_service_active(service_name): cmd(f'systemctl start {service_name}') @@ -103,17 +142,19 @@ def apply(wwan): break sleep(0.250) - # we only need the modem number. wwan0 -> 0, wwan1 -> 1 - modem = wwan['ifname'].lstrip('wwan') - base_cmd = f'mmcli --modem {modem}' - # Number of bearers is limited - always disconnect first - cmd(f'{base_cmd} --simple-disconnect') + if 'shutdown_required' in wwan: + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 + modem = wwan['ifname'].lstrip('wwan') + base_cmd = f'mmcli --modem {modem}' + # Number of bearers is limited - always disconnect first + cmd(f'{base_cmd} --simple-disconnect') w = WWANIf(wwan['ifname']) if 'deleted' in wwan or 'disable' in wwan: w.remove() - # There are no other WWAN interfaces - stop the daemon + # We are the last WWAN interface - there are no other WWAN interfaces + # remaining, thus we can stop ModemManager and free resources. if 'other_interfaces' not in wwan: cmd(f'systemctl stop {service_name}') # Clean CRON helper script which is used for to re-connect when @@ -123,27 +164,25 @@ def apply(wwan): return None - ip_type = 'ipv4' - slaac = dict_search('ipv6.address.autoconf', wwan) != None - if 'address' in wwan: - if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): - ip_type = 'ipv4v6' - elif 'dhcpv6' in wwan['address'] or slaac: - ip_type = 'ipv6' - elif 'dhcp' in wwan['address']: - ip_type = 'ipv4' - - options = f'ip-type={ip_type},apn=' + wwan['apn'] - if 'authentication' in wwan: - options += ',user={user},password={password}'.format(**wwan['authentication']) - - command = f'{base_cmd} --simple-connect="{options}"' - call(command, stdout=DEVNULL) - w.update(wwan) + if 'shutdown_required' in wwan: + ip_type = 'ipv4' + slaac = dict_search('ipv6.address.autoconf', wwan) != None + if 'address' in wwan: + if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): + ip_type = 'ipv4v6' + elif 'dhcpv6' in wwan['address'] or slaac: + ip_type = 'ipv6' + elif 'dhcp' in wwan['address']: + ip_type = 'ipv4' - if 'other_interfaces' not in wwan and 'deleted' in wwan: - cmd(f'systemctl start {service_name}') + options = f'ip-type={ip_type},apn=' + wwan['apn'] + if 'authentication' in wwan: + options += ',user={user},password={password}'.format(**wwan['authentication']) + command = f'{base_cmd} --simple-connect="{options}"' + call(command, stdout=DEVNULL) + + w.update(wwan) return None if __name__ == '__main__': diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 6b1d3bf1a..9d8fcfa36 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.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 @@ -114,10 +114,9 @@ def verify(policy): if 'prefix' not in rule_config: raise ConfigError(f'A prefix {mandatory_error}') - # Check prefix duplicates - if rule_config['prefix'] in entries and ('ge' not in rule_config and 'le' not in rule_config): - raise ConfigError(f'Prefix {rule_config["prefix"]} is duplicated!') - entries.append(rule_config['prefix']) + if rule_config in entries: + raise ConfigError(f'Rule "{rule}" contains a duplicate prefix definition!') + entries.append(rule_config) # route-maps tend to be a bit more complex so they get their own verify() section diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 64b113873..dace53d37 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -213,6 +213,11 @@ def verify(bgp): if 'non_exist_map' in afi_config['conditionally_advertise']: verify_route_map(afi_config['conditionally_advertise']['non_exist_map'], bgp) + # T4332: bgp deterministic-med cannot be disabled while addpath-tx-bestpath-per-AS is in use + if 'addpath_tx_per_as' in afi_config: + if dict_search('parameters.deterministic_med', bgp) == None: + raise ConfigError('addpath-tx-per-as requires BGP deterministic-med paramtere to be set!') + # Validate if configured Prefix list exists if 'prefix_list' in afi_config: for tmp in ['import', 'export']: diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 9b4b215de..f2501e38a 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -169,28 +169,40 @@ def verify(isis): # Segment routing checks if dict_search('segment_routing.global_block', isis): - high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) - low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) - # If segment routing global block high value is blank, throw error - if (low_label_value and not high_label_value) or (high_label_value and not low_label_value): - raise ConfigError('Segment routing global block requires both low and high value!') + # If segment routing global block high or low value is blank, throw error + if not (g_low_label_value or g_high_label_value): + raise ConfigError('Segment routing global-block requires both low and high value!') # If segment routing global block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing global block low value must be lower than high value') + if int(g_low_label_value) > int(g_high_label_value): + raise ConfigError('Segment routing global-block low value must be lower than high value') if dict_search('segment_routing.local_block', isis): - high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) - low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) + if dict_search('segment_routing.global_block', isis) == None: + raise ConfigError('Segment routing local-block requires global-block to be configured!') - # If segment routing local block high value is blank, throw error - if (low_label_value and not high_label_value) or (high_label_value and not low_label_value): - raise ConfigError('Segment routing local block requires both high and low value!') + l_high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) + l_low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) - # If segment routing local block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing local block low value must be lower than high value') + # If segment routing local-block high or low value is blank, throw error + if not (l_low_label_value or l_high_label_value): + raise ConfigError('Segment routing local-block requires both high and low value!') + + # If segment routing local-block low value is higher than the high value, throw error + if int(l_low_label_value) > int(l_high_label_value): + raise ConfigError('Segment routing local-block low value must be lower than high value') + + # local-block most live outside global block + global_range = range(int(g_low_label_value), int(g_high_label_value) +1) + local_range = range(int(l_low_label_value), int(l_high_label_value) +1) + + # Check for overlapping ranges + if list(set(global_range) & set(local_range)): + raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\ + f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') return None diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index cf447d4b5..dbe3be225 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -28,36 +28,33 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['traffic-policy'] + base = ['qos'] if not conf.exists(base): return None qos = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - for traffic_policy in ['drop-tail', 'fair-queue', 'fq-codel', 'limiter', - 'network-emulator', 'priority-queue', 'random-detect', - 'rate-control', 'round-robin', 'shaper', 'shaper-hfsc']: - traffic_policy_us = traffic_policy.replace('-','_') - # Individual policy type not present on CLI - no need to blend in - # any default values - if traffic_policy_us not in qos: - continue - - default_values = defaults(base + [traffic_policy_us]) - - # class is another tag node which requires individual handling - class_default_values = defaults(base + [traffic_policy_us, 'class']) - if 'class' in default_values: - del default_values['class'] - - for policy, policy_config in qos[traffic_policy_us].items(): - qos[traffic_policy_us][policy] = dict_merge( - default_values, qos[traffic_policy_us][policy]) - - if 'class' in policy_config: - for policy_class in policy_config['class']: - qos[traffic_policy_us][policy]['class'][policy_class] = dict_merge( - class_default_values, qos[traffic_policy_us][policy]['class'][policy_class]) + if 'policy' in qos: + for policy in qos['policy']: + # CLI mangles - to _ for better Jinja2 compatibility - do we need + # Jinja2 here? + policy = policy.replace('-','_') + + default_values = defaults(base + ['policy', policy]) + + # class is another tag node which requires individual handling + class_default_values = defaults(base + ['policy', policy, 'class']) + if 'class' in default_values: + del default_values['class'] + + for p_name, p_config in qos['policy'][policy].items(): + qos['policy'][policy][p_name] = dict_merge( + default_values, qos['policy'][policy][p_name]) + + if 'class' in p_config: + for p_class in p_config['class']: + qos['policy'][policy][p_name]['class'][p_class] = dict_merge( + class_default_values, qos['policy'][policy][p_name]['class'][p_class]) import pprint pprint.pprint(qos) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index f676fdbbe..2ebee8018 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.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 @@ -41,6 +41,7 @@ default_config_data = { 'interfaces': [], 'dnsv4': [], 'dnsv6': [], + 'client_named_ip_pool': [], 'client_ipv6_pool': [], 'client_ipv6_delegate_prefix': [], 'radius_server': [], @@ -219,6 +220,22 @@ def get_config(config=None): conf.set_level(base_path) + # Named client-ip-pool + if conf.exists(['client-ip-pool', 'name']): + for name in conf.list_nodes(['client-ip-pool', 'name']): + tmp = { + 'name': name, + 'gateway_address': '', + 'subnet': '' + } + + if conf.exists(['client-ip-pool', 'name', name, 'gateway-address']): + tmp['gateway_address'] += conf.return_value(['client-ip-pool', 'name', name, 'gateway-address']) + if conf.exists(['client-ip-pool', 'name', name, 'subnet']): + tmp['subnet'] += conf.return_value(['client-ip-pool', 'name', name, 'subnet']) + + ipoe['client_named_ip_pool'].append(tmp) + if conf.exists(['client-ipv6-pool', 'prefix']): for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']): tmp = { @@ -254,10 +271,6 @@ def verify(ipoe): if not ipoe['interfaces']: raise ConfigError('No IPoE interface configured') - for interface in ipoe['interfaces']: - if not interface['range']: - raise ConfigError(f'No IPoE client subnet defined on interface "{ interface }"') - if len(ipoe['dnsv4']) > 2: raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 7fb2dd1cf..26aacf46b 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -19,8 +19,6 @@ import os from sys import exit from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import leaf_node_changed -from vyos.util import call from vyos.util import dict_search from vyos.util import sysctl_write from vyos.util import write_file @@ -38,9 +36,6 @@ def get_config(config=None): opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - tmp = leaf_node_changed(conf, base + ['disable']) - if tmp: opt['reboot_required'] = {} - # 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) @@ -55,15 +50,6 @@ def generate(opt): pass def apply(opt): - # disable IPv6 globally - tmp = dict_search('disable', opt) - value = '1' if (tmp != None) else '0' - sysctl_write('net.ipv6.conf.all.disable_ipv6', value) - - if 'reboot_required' in opt: - print('Changing IPv6 disable parameter will only take affect\n' \ - 'when the system is rebooted.') - # configure multipath tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 4dd7f936d..c9c6aa187 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -23,6 +23,7 @@ from pwd import getpwall from pwd import getpwnam from spwd import getspnam from sys import exit +from time import sleep from vyos.config import Config from vyos.configdict import dict_merge @@ -31,6 +32,7 @@ from vyos.template import render from vyos.template import is_ipv4 from vyos.util import cmd from vyos.util import call +from vyos.util import run from vyos.util import DEVNULL from vyos.util import dict_search from vyos.xml import defaults @@ -250,13 +252,22 @@ def apply(login): if 'rm_users' in login: for user in login['rm_users']: try: + # Disable user to prevent re-login + call(f'usermod -s /sbin/nologin {user}') + # Logout user if he is still logged in if user in list(set([tmp[0] for tmp in users()])): print(f'{user} is logged in, forcing logout!') - call(f'pkill -HUP -u {user}') - - # Remove user account but leave home directory to be safe - call(f'userdel --remove {user}', stderr=DEVNULL) + # re-run command until user is logged out + while run(f'pkill -HUP -u {user}'): + sleep(0.250) + + # Remove user account but leave home directory in place. Re-run + # command until user is removed - userdel might return 8 as + # SSH sessions are not all yet properly cleaned away, thus we + # simply re-run the command until the account wen't away + while run(f'userdel --remove {user}', stderr=DEVNULL): + sleep(0.250) except Exception as e: raise ConfigError(f'Deleting user "{user}" raised exception: {e}') diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index c3e2d8efd..f79c8a21e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -30,7 +30,6 @@ from vyos.util import get_interface_config from vyos.util import popen from vyos.util import run from vyos.util import sysctl_write -from vyos.util import is_ipv6_enabled from vyos import ConfigError from vyos import frr from vyos import airbag @@ -219,8 +218,7 @@ def apply(vrf): # We also should add proper loopback IP addresses to the newly added # VRF for services bound to the loopback address (SNMP, NTP) vrf_if.add_addr('127.0.0.1/8') - if is_ipv6_enabled(): - vrf_if.add_addr('::1/128') + vrf_if.add_addr('::1/128') # add VRF description if available vrf_if.set_alias(config.get('description', '')) diff --git a/src/etc/cron.d/check-wwan b/src/etc/cron.d/check-wwan deleted file mode 100644 index 28190776f..000000000 --- a/src/etc/cron.d/check-wwan +++ /dev/null @@ -1 +0,0 @@ -*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py diff --git a/src/migration-scripts/system/22-to-23 b/src/migration-scripts/system/22-to-23 new file mode 100755 index 000000000..7f832e48a --- /dev/null +++ b/src/migration-scripts/system/22-to-23 @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit, 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', 'ipv6'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# T4346: drop support to disbale IPv6 address family within the OS Kernel +if config.exists(base + ['disable']): + config.delete(base + ['disable']) + # IPv6 address family disable was the only CLI option set - we can cleanup + # the entire tree + if len(config.list_nodes(base)) == 0: + config.delete(base) + +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) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 1000d8b72..c1b595412 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -352,7 +352,7 @@ class MultipartRoute(APIRoute): return error(e.status_code, e.detail) except Exception as e: if request.ERR_MISSING_KEY: - return error(422, "Valid API key is required") + return error(401, "Valid API key is required") if request.ERR_MISSING_DATA: return error(422, "Non-empty data field is required") if request.ERR_NOT_JSON: diff --git a/src/tests/test_util.py b/src/tests/test_util.py index 91890262c..8ac9a500a 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_util.py @@ -26,13 +26,3 @@ class TestVyOSUtil(TestCase): def test_sysctl_read(self): self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') - - def test_ipv6_enabled(self): - tmp = sysctl_read('net.ipv6.conf.all.disable_ipv6') - # We need to test for both variants as this depends on how the - # Docker container is started (with or without IPv6 support) - so we - # will simply check both cases to not make the users life miserable. - if tmp == '0': - self.assertTrue(is_ipv6_enabled()) - else: - self.assertFalse(is_ipv6_enabled()) |