diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/interfaces-bonding.py | 439 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-bridge.py | 411 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-dummy.py | 32 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-ethernet.py | 296 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-geneve.py | 133 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-l2tpv3.py | 260 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-loopback.py | 30 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-macsec.py | 61 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-pppoe.py | 47 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-pseudo-ethernet.py | 218 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wireguard.py | 12 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wireless.py | 688 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wirelessmodem.py | 54 | ||||
-rwxr-xr-x | src/conf_mode/nat.py | 17 | ||||
-rwxr-xr-x | src/op_mode/wireguard.py | 17 |
15 files changed, 534 insertions, 2181 deletions
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a16c4e105..3b238f1ea 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -16,41 +16,25 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BondIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data from vyos.config import Config -from vyos.util import call, cmd -from vyos.validate import is_member, has_address_configured +from vyos.configdict import get_interface_dict +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_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.validate import is_member +from vyos.validate import has_address_configured from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'arp_mon_intvl': 0, - 'arp_mon_tgt': [], - 'deleted': False, - 'hash_policy': 'layer2', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'mode': '802.3ad', - 'member': [], - 'shutdown_required': False, - 'primary': '', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], -} - - def get_bond_mode(mode): if mode == 'round-robin': return 'balance-rr' @@ -67,339 +51,138 @@ def get_bond_mode(mode): elif mode == 'adaptive-load-balance': return 'balance-alb' else: - raise ConfigError('invalid bond mode "{}"'.format(mode)) + raise ConfigError(f'invalid bond mode "{mode}"') def get_config(): - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'bonding'] + 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 + # later on for wach member + if 'member' in bond and 'interface' in bond['member']: + # first convert it to a list if only one member is given + if isinstance(bond['member']['interface'], str): + bond['member']['interface'] = [bond['member']['interface']] + + tmp={} + for interface in bond['member']['interface']: + tmp.update({interface: {}}) + + bond['member']['interface'] = tmp + + if 'mode' in bond: + bond['mode'] = get_bond_mode(bond['mode']) + + tmp = leaf_node_changed(conf, ['mode']) + if tmp: + bond.update({'shutdown_required': ''}) + + # determine which members have been removed + tmp = leaf_node_changed(conf, ['member', 'interface']) + if tmp: + bond.update({'shutdown_required': ''}) + if 'member' in bond: + bond['member'].update({'interface_remove': tmp }) + else: + bond.update({'member': {'interface_remove': tmp }}) + + if 'member' in bond and 'interface' in bond['member']: + for interface, interface_config in bond['member']['interface'].items(): + # Check if we are a member of another bond device + tmp = is_member(conf, interface, 'bridge') + if tmp: + interface_config.update({'is_bridge_member' : tmp}) + + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp and tmp != bond['ifname']: + interface_config.update({'is_bond_member' : tmp}) - # initialize kernel module if not loaded - if not os.path.isfile('/sys/class/net/bonding_masters'): - import syslog - syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") - if call('modprobe bonding max_bonds=0 miimon=250') != 0: - syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") - raise ConfigError("failed loading bonding kernel module") - - # check if bond has been removed - cfg_base = 'interfaces bonding ' + ifname - if not conf.exists(cfg_base): - bond = deepcopy(default_config_data) - bond['intf'] = ifname - bond['deleted'] = True - return bond - - # set new configuration level - conf.set_level(cfg_base) - - bond, disabled = intf_to_dict(conf, default_config_data) - - # ARP link monitoring frequency in milliseconds - if conf.exists('arp-monitor interval'): - bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) - - # IP address to use for ARP monitoring - if conf.exists('arp-monitor target'): - bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') - - # Bonding transmit hash policy - if conf.exists('hash-policy'): - bond['hash_policy'] = conf.return_value('hash-policy') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - bond['ip_proxy_arp_pvlan'] = 1 - - # Bonding mode - if conf.exists('mode'): - act_mode = conf.return_value('mode') - eff_mode = conf.return_effective_value('mode') - if not (act_mode == eff_mode): - bond['shutdown_required'] = True - - bond['mode'] = get_bond_mode(act_mode) - - # determine bond member interfaces (currently configured) - bond['member'] = conf.return_values('member interface') - - # We can not call conf.return_effective_values() as it would not work - # on reboots. Reboots/First boot will return that running config and - # saved config is the same, thus on a reboot the bond members will - # not be added all (https://phabricator.vyos.net/T2030) - live_members = BondIf(bond['intf']).get_slaves() - if not (bond['member'] == live_members): - bond['shutdown_required'] = True - - # Primary device interface - if conf.exists('primary'): - bond['primary'] = conf.return_value('primary') - - add_to_dict(conf, disabled, bond, 'vif', 'vif') - add_to_dict(conf, disabled, bond, 'vif-s', 'vif_s') + # bond members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bond def verify(bond): - if bond['deleted']: - if bond['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{bond["intf"]}" as it is a ' - f'member of bridge "{bond["is_bridge_member"]}"!')) - + if 'deleted' in bond: + verify_bridge_delete(bond) return None - if len(bond['arp_mon_tgt']) > 16: - raise ConfigError('The maximum number of arp-monitor targets is 16') + if 'arp_monitor' in bond: + if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') + + if 'interval' in bond['arp_monitor'] and len(int(bond['arp_monitor']['interval'])) > 0: + if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: + raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ + 'transmit-load-balance or adaptive-load-balance') - if bond['primary']: + if 'primary' in bond: if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError(( - 'Mode dependency failed, primary not supported in mode ' - f'"{bond["mode"]}"!')) - - if ( bond['is_bridge_member'] - and ( bond['address'] - or bond['ipv6_eui64_prefix'] - or bond['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{bond["intf"]}" ' - f'as it is a member of bridge "{bond["is_bridge_member"]}"!')) - - if bond['vrf']: - if bond['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') - - if bond['is_bridge_member']: - raise ConfigError(( - f'Interface "{bond["intf"]}" cannot be member of VRF ' - f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} ' - f'at the same time!')) + raise ConfigError('Option primary - mode dependency failed, not' + 'supported in mode {mode}!'.format(**bond)) + + verify_address(bond) + verify_dhcpv6(bond) + verify_vrf(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) - conf = Config() - for intf in bond['member']: - # check if member interface is "real" - if intf not in interfaces(): - raise ConfigError(f'Interface {intf} does not exist!') - - # a bonding member interface is only allowed to be assigned to one bond! - all_bonds = conf.list_nodes('interfaces bonding') - # We do not need to check our own bond - all_bonds.remove(bond['intf']) - for tmp in all_bonds: - if conf.exists('interfaces bonding {tmp} member interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bond "{tmp}"!')) - - # can not add interfaces with an assigned address to a bond - if has_address_configured(conf, intf): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it has an address assigned!')) - - # bond members are not allowed to be bridge members - tmp = is_member(conf, intf, 'bridge') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bridge "{tmp}"!')) - - # bond members are not allowed to be vrrp members - for tmp in conf.list_nodes('high-availability vrrp group'): - if conf.exists('high-availability vrrp group {tmp} interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of VRRP group "{tmp}"!')) - - # bond members are not allowed to be underlaying psuedo-ethernet devices - for tmp in conf.list_nodes('interfaces pseudo-ethernet'): - if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of pseudo-ethernet "{tmp}"!')) - - # bond members are not allowed to be underlaying vxlan devices - for tmp in conf.list_nodes('interfaces vxlan'): - if conf.exists('interfaces vxlan {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of VXLAN "{tmp}"!')) - - if bond['primary']: - if bond['primary'] not in bond['member']: - raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member') + bond_name = bond['ifname'] + if 'member' in bond: + member = bond.get('member') + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bond') + + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') + + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + + if 'primary' in bond: + if bond['primary'] not in bond['member']['interface']: + raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: raise ConfigError('primary interface only works for mode active-backup, ' \ 'transmit-load-balance or adaptive-load-balance') - if bond['arp_mon_intvl'] > 0: - if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: - raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ - 'transmit-load-balance or adaptive-load-balance') - return None def generate(bond): return None def apply(bond): - b = BondIf(bond['intf']) + b = BondIf(bond['ifname']) - if bond['deleted']: + if 'deleted' in bond: # delete interface b.remove() else: - # ARP link monitoring frequency, reset miimon when arp-montior is inactive - # this is done inside BondIf automatically - b.set_arp_interval(bond['arp_mon_intvl']) - - # ARP monitor targets need to be synchronized between sysfs and CLI. - # Unfortunately an address can't be send twice to sysfs as this will - # result in the following exception: OSError: [Errno 22] Invalid argument. - # - # We remove ALL adresses prior adding new ones, this will remove addresses - # added manually by the user too - but as we are limited to 16 adresses - # from the kernel side this looks valid to me. We won't run into an error - # when a user added manual adresses which would result in having more - # then 16 adresses in total. - arp_tgt_addr = list(map(str, b.get_arp_ip_target().split())) - for addr in arp_tgt_addr: - b.set_arp_ip_target('-' + addr) - - # Add configured ARP target addresses - for addr in bond['arp_mon_tgt']: - b.set_arp_ip_target('+' + addr) - - # update interface description used e.g. within SNMP - b.set_alias(bond['description']) - - if bond['dhcp_client_id']: - b.dhcp.v4.options['client_id'] = bond['dhcp_client_id'] - - if bond['dhcp_hostname']: - b.dhcp.v4.options['hostname'] = bond['dhcp_hostname'] - - if bond['dhcp_vendor_class_id']: - b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id'] - - if bond['dhcpv6_prm_only']: - b.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bond['dhcpv6_temporary']: - b.dhcp.v6.options['dhcpv6_temporary'] = True - - if bond['dhcpv6_pd_length']: - b.dhcp.v6.options['dhcpv6_pd_length'] = bond['dhcpv6_pd_length'] - - if bond['dhcpv6_pd_interfaces']: - b.dhcp.v6.options['dhcpv6_pd_interfaces'] = bond['dhcpv6_pd_interfaces'] - - # ignore link state changes - b.set_link_detect(bond['disable_link_detect']) - # Bonding transmit hash policy - b.set_hash_policy(bond['hash_policy']) - # configure ARP cache timeout in milliseconds - b.set_arp_cache_tmo(bond['ip_arp_cache_tmo']) - # configure ARP filter configuration - b.set_arp_filter(bond['ip_disable_arp_filter']) - # configure ARP accept - b.set_arp_accept(bond['ip_enable_arp_accept']) - # configure ARP announce - b.set_arp_announce(bond['ip_enable_arp_announce']) - # configure ARP ignore - b.set_arp_ignore(bond['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - b.set_proxy_arp(bond['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan']) - # IPv6 accept RA - b.set_ipv6_accept_ra(bond['ipv6_accept_ra']) - # IPv6 address autoconfiguration - b.set_ipv6_autoconf(bond['ipv6_autoconf']) - # IPv6 forwarding - b.set_ipv6_forwarding(bond['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - b.set_ipv6_dad_messages(bond['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in bond['ipv6_eui64_prefix_remove']: - b.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if bond['mac']: - b.set_mac(bond['mac']) - - # Add IPv6 EUI-based addresses - for addr in bond['ipv6_eui64_prefix']: - b.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - b.set_mtu(bond['mtu']) - - # Primary device interface - if bond['primary']: - b.set_primary(bond['primary']) - - # Some parameters can not be changed when the bond is up. - if bond['shutdown_required']: - # Disable bond prior changing of certain properties - b.set_admin_state('down') - - # The bonding mode can not be changed when there are interfaces enslaved - # to this bond, thus we will free all interfaces from the bond first! - for intf in b.get_slaves(): - b.del_port(intf) - - # Bonding policy/mode - b.set_mode(bond['mode']) - - # Add (enslave) interfaces to bond - for intf in bond['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{intf}"') - b.add_port(intf) - - # As the bond interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not bond['disable']: - b.set_admin_state('up') - else: - b.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bond['address_remove']: - b.del_addr(addr) - for addr in bond['address']: - b.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not bond['is_bridge_member']: - b.set_vrf(bond['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if bond['is_bridge_member']: - b.add_to_bridge(bond['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(b, bond) + b.update(bond) return None diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 1e4fa5816..ee8e85e73 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -16,251 +16,102 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BridgeIf, Section -from vyos.ifconfig.stp import STP -from vyos.configdict import list_diff, interface_default_data -from vyos.validate import is_member, has_address_configured from vyos.config import Config -from vyos.util import cmd, get_bridge_member_config +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_vrf +from vyos.ifconfig import BridgeIf +from vyos.validate import is_member, has_address_configured +from vyos.xml import defaults + +from vyos.util import cmd from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'aging': 300, - 'arp_cache_tmo': 30, - 'deleted': False, - 'forwarding_delay': 14, - 'hello_time': 2, - 'igmp_querier': 0, - 'intf': '', - 'max_age': 20, - 'member': [], - 'member_remove': [], - 'priority': 32768, - 'stp': 0 -} - def get_config(): - bridge = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if bridge has been removed - if not conf.exists('interfaces bridge ' + bridge['intf']): - bridge['deleted'] = True - return bridge - - # set new configuration level - conf.set_level('interfaces bridge ' + bridge['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - bridge['address'] = conf.return_values('address') - - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('address') - bridge['address_remove'] = list_diff(eff_addr, bridge['address']) - - # retrieve aging - how long addresses are retained - if conf.exists('aging'): - bridge['aging'] = int(conf.return_value('aging')) - - # retrieve interface description - if conf.exists('description'): - bridge['description'] = conf.return_value('description') - - # get DHCP client identifier - if conf.exists('dhcp-options client-id'): - bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id') - - # DHCP client host name (overrides the system host name) - if conf.exists('dhcp-options host-name'): - bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - - # DHCP client vendor identifier - if conf.exists('dhcp-options vendor-class-id'): - bridge['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') - - # DHCPv6 only acquire config parameters, no address - if conf.exists('dhcpv6-options parameters-only'): - bridge['dhcpv6_prm_only'] = True - - # DHCPv6 temporary IPv6 address - if conf.exists('dhcpv6-options temporary'): - bridge['dhcpv6_temporary'] = True - - # Disable this bridge interface - if conf.exists('disable'): - bridge['disable'] = True - - # Ignore link state changes - if conf.exists('disable-link-detect'): - bridge['disable_link_detect'] = 2 - - # Forwarding delay - if conf.exists('forwarding-delay'): - bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay')) - - # Hello packet advertisment interval - if conf.exists('hello-time'): - bridge['hello_time'] = int(conf.return_value('hello-time')) - - # Enable Internet Group Management Protocol (IGMP) querier - if conf.exists('igmp querier'): - bridge['igmp_querier'] = 1 - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - bridge['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - bridge['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - bridge['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - bridge['ip_enable_arp_ignore'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - bridge['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - bridge['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Determine currently effective EUI64 addresses - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('ipv6 address eui64') - bridge['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, bridge['ipv6_eui64_prefix']) - - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): - bridge['ipv6_eui64_prefix_remove'].append('fe80::/64') - else: - # add the link-local by default to make IPv6 work - bridge['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - bridge['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - bridge['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # Media Access Control (MAC) address - if conf.exists('mac'): - bridge['mac'] = conf.return_value('mac') - - # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses - # before re-adding them - if ( bridge['mac'] and bridge['intf'] in Section.interfaces(section='bridge') - and bridge['mac'] != BridgeIf(bridge['intf'], create=False).get_mac() ): - bridge['ipv6_eui64_prefix_remove'] += bridge['ipv6_eui64_prefix'] - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if bridge['ipv6_autoconf'] or 'dhcpv6' in bridge['address']: - bridge['ipv6_accept_ra'] = 2 - - # Interval at which neighbor bridges are removed - if conf.exists('max-age'): - bridge['max_age'] = int(conf.return_value('max-age')) - - # Determine bridge member interface (currently configured) - for intf in conf.list_nodes('member interface'): - # defaults are stored in util.py (they can't be here as all interface - # scripts use the function) - memberconf = get_bridge_member_config(conf, bridge['intf'], intf) - if memberconf: - memberconf['name'] = intf - bridge['member'].append(memberconf) - - # Determine bridge member interface (currently effective) - to determine which - # interfaces is no longer assigend to the bridge and thus can be removed - eff_intf = conf.list_effective_nodes('member interface') - act_intf = conf.list_nodes('member interface') - bridge['member_remove'] = list_diff(eff_intf, act_intf) - - # Priority for this bridge - if conf.exists('priority'): - bridge['priority'] = int(conf.return_value('priority')) - - # Enable spanning tree protocol - if conf.exists('stp'): - bridge['stp'] = 1 - - # retrieve VRF instance - if conf.exists('vrf'): - bridge['vrf'] = conf.return_value('vrf') + base = ['interfaces', 'bridge'] + bridge = get_interface_dict(conf, base) + + # determine which members have been removed + tmp = node_changed(conf, ['member', 'interface']) + if tmp: + if 'member' in bridge: + bridge['member'].update({'interface_remove': tmp }) + else: + bridge.update({'member': {'interface_remove': tmp }}) + + if 'member' in bridge and 'interface' in bridge['member']: + # XXX TT2665 we need a copy of the dict keys for iteration, else we will get: + # RuntimeError: dictionary changed size during iteration + for interface in list(bridge['member']['interface']): + for key in ['cost', 'priority']: + if interface == key: + del bridge['member']['interface'][key] + continue + + # the default dictionary is not properly paged into the dict (see T2665) + # thus we will ammend it ourself + default_member_values = defaults(base + ['member', 'interface']) + for interface, interface_config in bridge['member']['interface'].items(): + interface_config.update(default_member_values) + + # Check if we are a member of another bridge device + tmp = is_member(conf, interface, 'bridge') + if tmp and tmp != bridge['ifname']: + interface_config.update({'is_bridge_member' : tmp}) + + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp: + interface_config.update({'is_bond_member' : tmp}) + + # Bridge members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bridge def verify(bridge): - if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if 'deleted' in bridge: + return None - vrf_name = bridge['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + verify_dhcpv6(bridge) + verify_vrf(bridge) - conf = Config() - for intf in bridge['member']: - # the interface must exist prior adding it to a bridge - if intf['name'] not in interfaces(): - raise ConfigError(( - f'Cannot add nonexistent interface "{intf["name"]}" ' - f'to bridge "{bridge["intf"]}"')) + if 'member' in bridge: + member = bridge.get('member') + bridge_name = bridge['ifname'] + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bridge "{bridge_name}", ' - if intf['name'] == 'lo': - raise ConfigError('Loopback interface "lo" can not be added to a bridge') + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bridge') - # bridge members aren't allowed to be members of another bridge - for br in conf.list_nodes('interfaces bridge'): - # it makes no sense to verify ourself in this case - if br == bridge['intf']: - continue + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') - tmp = conf.list_nodes(f'interfaces bridge {br} member interface') - if intf['name'] in tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bridge "{br}"!')) + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') - # bridge members are not allowed to be bond members - tmp = is_member(conf, intf['name'], 'bonding') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!')) + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') - # bridge members must not have an assigned address - if has_address_configured(conf, intf['name']): - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it has an address assigned!')) + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') return None @@ -268,120 +119,12 @@ def generate(bridge): return None def apply(bridge): - br = BridgeIf(bridge['intf']) - - if bridge['deleted']: + br = BridgeIf(bridge['ifname']) + if 'deleted' in bridge: # delete interface br.remove() else: - # enable interface - br.set_admin_state('up') - # set ageing time - br.set_ageing_time(bridge['aging']) - # set bridge forward delay - br.set_forward_delay(bridge['forwarding_delay']) - # set hello time - br.set_hello_time(bridge['hello_time']) - # configure ARP filter configuration - br.set_arp_filter(bridge['ip_disable_arp_filter']) - # configure ARP accept - br.set_arp_accept(bridge['ip_enable_arp_accept']) - # configure ARP announce - br.set_arp_announce(bridge['ip_enable_arp_announce']) - # configure ARP ignore - br.set_arp_ignore(bridge['ip_enable_arp_ignore']) - # IPv6 accept RA - br.set_ipv6_accept_ra(bridge['ipv6_accept_ra']) - # IPv6 address autoconfiguration - br.set_ipv6_autoconf(bridge['ipv6_autoconf']) - # IPv6 forwarding - br.set_ipv6_forwarding(bridge['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - br.set_ipv6_dad_messages(bridge['ipv6_dup_addr_detect']) - # set max message age - br.set_max_age(bridge['max_age']) - # set bridge priority - br.set_priority(bridge['priority']) - # turn stp on/off - br.set_stp(bridge['stp']) - # enable or disable IGMP querier - br.set_multicast_querier(bridge['igmp_querier']) - # update interface description used e.g. within SNMP - br.set_alias(bridge['description']) - - if bridge['dhcp_client_id']: - br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id'] - - if bridge['dhcp_hostname']: - br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname'] - - if bridge['dhcp_vendor_class_id']: - br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id'] - - if bridge['dhcpv6_prm_only']: - br.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bridge['dhcpv6_temporary']: - br.dhcp.v6.options['dhcpv6_temporary'] = True - - if bridge['dhcpv6_pd_length']: - br.dhcp.v6.options['dhcpv6_pd_length'] = br['dhcpv6_pd_length'] - - if bridge['dhcpv6_pd_interfaces']: - br.dhcp.v6.options['dhcpv6_pd_interfaces'] = br['dhcpv6_pd_interfaces'] - - # assign/remove VRF - br.set_vrf(bridge['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - # (adding members to a fresh bridge changes its MAC too) - for addr in bridge['ipv6_eui64_prefix_remove']: - br.del_ipv6_eui64_address(addr) - - # remove interface from bridge - for intf in bridge['member_remove']: - br.del_port(intf) - - # add interfaces to bridge - for member in bridge['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{member["name"]}"') - br.add_port(member['name']) - - # Change interface MAC address - if bridge['mac']: - br.set_mac(bridge['mac']) - - # Add IPv6 EUI-based addresses (must be done after adding the - # 1st bridge member or setting its MAC) - for addr in bridge['ipv6_eui64_prefix']: - br.add_ipv6_eui64_address(addr) - - # up/down interface - if bridge['disable']: - br.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bridge['address_remove']: - br.del_addr(addr) - for addr in bridge['address']: - br.add_addr(addr) - - STPBridgeIf = STP.enable(BridgeIf) - # configure additional bridge member options - for member in bridge['member']: - i = STPBridgeIf(member['name']) - # configure ARP cache timeout - i.set_arp_cache_tmo(member['arp_cache_tmo']) - # ignore link state changes - i.set_link_detect(member['disable_link_detect']) - # set bridge port path cost - i.set_path_cost(member['cost']) - # set bridge port path priority - i.set_path_priority(member['priority']) + br.update(bridge) return None diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 2d62420a6..8df86c8ea 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -19,41 +19,23 @@ import os from sys import exit from vyos.config import Config +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.ifconfig import DummyIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = ['interfaces', 'dummy', ifname] - - dummy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if dummy == {}: - dummy.update({'deleted' : ''}) - - # store interface instance name in dictionary - dummy.update({'ifname': ifname}) - - # check if we are a member of any bridge - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - dummy.update(tmp) - + base = ['interfaces', 'dummy'] + 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 8b895c4d2..10758e35a 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -17,295 +17,65 @@ import os from sys import exit -from copy import deepcopy -from netifaces import interfaces -from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.validate import is_member from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_address +from vyos.configverify import verify_vrf +from vyos.configverify import verify_vlan_config +from vyos.ifconfig import EthernetIf from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'duplex': 'auto', - 'flow_control': 'on', - 'hw_id': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'is_bond_member': False, - 'intf': '', - 'offload_gro': 'off', - 'offload_gso': 'off', - 'offload_sg': 'off', - 'offload_tso': 'off', - 'offload_ufo': 'off', - 'speed': 'auto', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], - 'vrf': '' -} - - def get_config(): - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'ethernet'] + ethernet = get_interface_dict(conf, base) + return ethernet - # check if ethernet interface has been removed - cfg_base = ['interfaces', 'ethernet', ifname] - if not conf.exists(cfg_base): - eth = deepcopy(default_config_data) - eth['intf'] = ifname - eth['deleted'] = True - # we can not bail out early as ethernet interface can not be removed - # Kernel will complain with: RTNETLINK answers: Operation not supported. - # Thus we need to remove individual settings - return eth - - # set new configuration level - conf.set_level(cfg_base) - - eth, disabled = intf_to_dict(conf, default_config_data) - - # disable ethernet flow control (pause frames) - if conf.exists('disable-flow-control'): - eth['flow_control'] = 'off' - - # retrieve real hardware address - if conf.exists('hw-id'): - eth['hw_id'] = conf.return_value('hw-id') - - # interface duplex - if conf.exists('duplex'): - eth['duplex'] = conf.return_value('duplex') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - eth['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - eth['ip_proxy_arp_pvlan'] = 1 - - # check if we are a member of any bond - eth['is_bond_member'] = is_member(conf, eth['intf'], 'bonding') - - # GRO (generic receive offload) - if conf.exists('offload-options generic-receive'): - eth['offload_gro'] = conf.return_value('offload-options generic-receive') - - # GSO (generic segmentation offload) - if conf.exists('offload-options generic-segmentation'): - eth['offload_gso'] = conf.return_value('offload-options generic-segmentation') - - # scatter-gather option - if conf.exists('offload-options scatter-gather'): - eth['offload_sg'] = conf.return_value('offload-options scatter-gather') - - # TSO (TCP segmentation offloading) - if conf.exists('offload-options tcp-segmentation'): - eth['offload_tso'] = conf.return_value('offload-options tcp-segmentation') - - # UDP fragmentation offloading - if conf.exists('offload-options udp-fragmentation'): - eth['offload_ufo'] = conf.return_value('offload-options udp-fragmentation') - - # interface speed - if conf.exists('speed'): - eth['speed'] = conf.return_value('speed') - - # remove default IPv6 link-local address if member of a bond - if eth['is_bond_member'] and 'fe80::/64' in eth['ipv6_eui64_prefix']: - eth['ipv6_eui64_prefix'].remove('fe80::/64') - eth['ipv6_eui64_prefix_remove'].append('fe80::/64') - - add_to_dict(conf, disabled, eth, 'vif', 'vif') - add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s') - - return eth - - -def verify(eth): - if eth['deleted']: +def verify(ethernet): + if 'deleted' in ethernet: return None - if eth['intf'] not in interfaces(): - raise ConfigError(f"Interface ethernet {eth['intf']} does not exist") + verify_interface_exists(ethernet) - if eth['speed'] == 'auto': - if eth['duplex'] != 'auto': + if ethernet.get('speed', None) == 'auto': + if ethernet.get('duplex', None) != 'auto': raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too') - if eth['duplex'] == 'auto': - if eth['speed'] != 'auto': + if ethernet.get('duplex', None) == 'auto': + if ethernet.get('speed', None) != 'auto': raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') - if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - - memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member'] - - if ( memberof - and ( eth['address'] - or eth['ipv6_eui64_prefix'] - or eth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{eth["intf"]}" ' - f'as it is a member of "{memberof}"!')) + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) - if eth['vrf']: - if eth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{eth["vrf"]}" does not exist') - - if memberof: - raise ConfigError(( - f'Interface "{eth["intf"]}" cannot be member of VRF "{eth["vrf"]}" ' - f'and "{memberof}" at the same time!')) - - if eth['mac'] and eth['is_bond_member']: - print('WARNING: "mac {0}" command will be ignored because {1} is a part of {2}'\ - .format(eth['mac'], eth['intf'], eth['is_bond_member'])) + 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)) # use common function to verify VLAN configuration - verify_vlan_config(eth) + verify_vlan_config(ethernet) return None -def generate(eth): +def generate(ethernet): return None -def apply(eth): - e = EthernetIf(eth['intf']) - if eth['deleted']: - # apply all vlans to interface (they need removing too) - apply_all_vlans(e, eth) - +def apply(ethernet): + e = EthernetIf(ethernet['ifname']) + if 'deleted' in ethernet: # delete interface e.remove() else: - # update interface description used e.g. within SNMP - e.set_alias(eth['description']) - - if eth['dhcp_client_id']: - e.dhcp.v4.options['client_id'] = eth['dhcp_client_id'] - - if eth['dhcp_hostname']: - e.dhcp.v4.options['hostname'] = eth['dhcp_hostname'] - - if eth['dhcp_vendor_class_id']: - e.dhcp.v4.options['vendor_class_id'] = eth['dhcp_vendor_class_id'] - - if eth['dhcpv6_prm_only']: - e.dhcp.v6.options['dhcpv6_prm_only'] = True - - if eth['dhcpv6_temporary']: - e.dhcp.v6.options['dhcpv6_temporary'] = True - - if eth['dhcpv6_pd_length']: - e.dhcp.v6.options['dhcpv6_pd_length'] = eth['dhcpv6_pd_length'] - - if eth['dhcpv6_pd_interfaces']: - e.dhcp.v6.options['dhcpv6_pd_interfaces'] = eth['dhcpv6_pd_interfaces'] - - # ignore link state changes - e.set_link_detect(eth['disable_link_detect']) - # disable ethernet flow control (pause frames) - e.set_flow_control(eth['flow_control']) - # configure ARP cache timeout in milliseconds - e.set_arp_cache_tmo(eth['ip_arp_cache_tmo']) - # configure ARP filter configuration - e.set_arp_filter(eth['ip_disable_arp_filter']) - # configure ARP accept - e.set_arp_accept(eth['ip_enable_arp_accept']) - # configure ARP announce - e.set_arp_announce(eth['ip_enable_arp_announce']) - # configure ARP ignore - e.set_arp_ignore(eth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - e.set_proxy_arp(eth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - e.set_proxy_arp_pvlan(eth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - e.set_ipv6_accept_ra(eth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - e.set_ipv6_autoconf(eth['ipv6_autoconf']) - # IPv6 forwarding - e.set_ipv6_forwarding(eth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - e.set_ipv6_dad_messages(eth['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in eth['ipv6_eui64_prefix_remove']: - e.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if not eth['is_bond_member']: - if eth['mac']: - e.set_mac(eth['mac']) - elif eth['hw_id']: - e.set_mac(eth['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in eth['ipv6_eui64_prefix']: - e.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - e.set_mtu(eth['mtu']) - - # GRO (generic receive offload) - e.set_gro(eth['offload_gro']) - - # GSO (generic segmentation offload) - e.set_gso(eth['offload_gso']) - - # scatter-gather option - e.set_sg(eth['offload_sg']) - - # TSO (TCP segmentation offloading) - e.set_tso(eth['offload_tso']) - - # UDP fragmentation offloading - e.set_ufo(eth['offload_ufo']) - - # Set physical interface speed and duplex - e.set_speed_duplex(eth['speed'], eth['duplex']) - - # Enable/Disable interface - if eth['disable']: - e.set_admin_state('down') - else: - e.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in eth['address_remove']: - e.del_addr(addr) - for addr in eth['address']: - e.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge or bond, - # otherwise 'nomaster' removes it from it) - if not ( eth['is_bridge_member'] or eth['is_bond_member'] ): - e.set_vrf(eth['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if eth['is_bridge_member']: - e.add_to_bridge(eth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(e, eth) + e.update(ethernet) if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 31f6eb6b5..1104bd3c0 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 VyOS maintainers and contributors +# Copyright (C) 2019-2020 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -21,102 +21,37 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete from vyos.ifconfig import GeneveIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp': 0, - 'is_bridge_member': False, - 'mtu': 1500, - 'remote': '', - 'vni': '' -} - def get_config(): - geneve = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - geneve['is_bridge_member'] = is_member(conf, geneve['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces geneve ' + geneve['intf']): - geneve['deleted'] = True - return geneve - - # set new configuration level - conf.set_level('interfaces geneve ' + geneve['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - geneve['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - geneve['description'] = conf.return_value('description') - - # Disable this interface - if conf.exists('disable'): - geneve['disable'] = True - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - geneve['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable proxy-arp on this interface - if conf.exists('ip enable-proxy-arp'): - geneve['ip_proxy_arp'] = 1 - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - geneve['mtu'] = int(conf.return_value('mtu')) - - # Remote address of GENEVE tunnel - if conf.exists('remote'): - geneve['remote'] = conf.return_value('remote') - - # Virtual Network Identifier - if conf.exists('vni'): - geneve['vni'] = conf.return_value('vni') - + base = ['interfaces', 'geneve'] + geneve = get_interface_dict(conf, base) return geneve - def verify(geneve): - if geneve['deleted']: - if geneve['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{geneve["intf"]}" as it is a ' - f'member of bridge "{geneve["is_bridge_member"]}"!')) - + if 'deleted' in geneve: + verify_bridge_delete(geneve) return None - if geneve['is_bridge_member'] and geneve['address']: - raise ConfigError(( - f'Cannot assign address to interface "{geneve["intf"]}" ' - f'as it is a member of bridge "{geneve["is_bridge_member"]}"!')) + verify_address(geneve) - if not geneve['remote']: - raise ConfigError('GENEVE remote must be configured') + if 'remote' not in geneve: + raise ConfigError('Remote side must be configured') - if not geneve['vni']: - raise ConfigError('GENEVE VNI must be configured') + if 'vni' not in geneve: + raise ConfigError('VNI must be configured') return None @@ -127,13 +62,13 @@ def generate(geneve): def apply(geneve): # Check if GENEVE interface already exists - if geneve['intf'] in interfaces(): - g = GeneveIf(geneve['intf']) + 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 not geneve['deleted']: + if 'deleted' not in geneve: # GENEVE interface needs to be created on-block # instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig @@ -144,32 +79,8 @@ def apply(geneve): conf['remote'] = geneve['remote'] # Finally create the new interface - g = GeneveIf(geneve['intf'], **conf) - # update interface description used e.g. by SNMP - g.set_alias(geneve['description']) - # Maximum Transfer Unit (MTU) - g.set_mtu(geneve['mtu']) - - # configure ARP cache timeout in milliseconds - g.set_arp_cache_tmo(geneve['ip_arp_cache_tmo']) - # Enable proxy-arp on this interface - g.set_proxy_arp(geneve['ip_proxy_arp']) - - # Configure interface address(es) - no need to implicitly delete the - # old addresses as they have already been removed by deleting the - # interface above - for addr in geneve['address']: - g.add_addr(addr) - - # As the GENEVE interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not geneve['disable']: - g.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if geneve['is_bridge_member']: - g.add_to_bridge(geneve['is_bridge_member']) + g = GeneveIf(geneve['ifname'], **conf) + g.update(geneve) return None diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 4ff0bcb57..0978df5b6 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -21,200 +21,65 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.ifconfig import L2TPv3If, Interface +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.ifconfig import L2TPv3If +from vyos.util import check_kmod +from vyos.validate import is_addr_assigned from vyos import ConfigError -from vyos.util import call -from vyos.validate import is_member, is_addr_assigned - from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'encapsulation': 'udp', - 'local_address': '', - 'local_port': 5000, - 'intf': '', - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'mtu': 1488, - 'peer_session_id': '', - 'peer_tunnel_id': '', - 'remote_address': '', - 'remote_port': 5000, - 'session_id': '', - 'tunnel_id': '' -} +k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] -def check_kmod(): - modules = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') def get_config(): - l2tpv3 = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'l2tpv3'] + l2tpv3 = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - l2tpv3['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member of a bridge - l2tpv3['is_bridge_member'] = is_member(conf, l2tpv3['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces l2tpv3 ' + l2tpv3['intf']): - l2tpv3['deleted'] = True - interface = l2tpv3['intf'] - - # to delete the l2tpv3 interface we need the current tunnel_id and session_id - if conf.exists_effective(f'interfaces l2tpv3 {interface} tunnel-id'): - l2tpv3['tunnel_id'] = conf.return_effective_value(f'interfaces l2tpv3 {interface} tunnel-id') - - if conf.exists_effective(f'interfaces l2tpv3 {interface} session-id'): - l2tpv3['session_id'] = conf.return_effective_value(f'interfaces l2tpv3 {interface} session-id') - - return l2tpv3 - - # set new configuration level - conf.set_level('interfaces l2tpv3 ' + l2tpv3['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - l2tpv3['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - l2tpv3['description'] = conf.return_value('description') - - # get tunnel destination port - if conf.exists('destination-port'): - l2tpv3['remote_port'] = int(conf.return_value('destination-port')) - - # Disable this interface - if conf.exists('disable'): - l2tpv3['disable'] = True - - # get tunnel encapsulation type - if conf.exists('encapsulation'): - l2tpv3['encapsulation'] = conf.return_value('encapsulation') - - # get tunnel local ip address - if conf.exists('local-ip'): - l2tpv3['local_address'] = conf.return_value('local-ip') - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - l2tpv3['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - l2tpv3['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Remove the default link-local address if set. - if not ( conf.exists('ipv6 address no-default-link-local') or - l2tpv3['is_bridge_member'] ): - # add the link-local by default to make IPv6 work - l2tpv3['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - l2tpv3['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - l2tpv3['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) + # L2TPv3 is "special" the default MTU is 1488 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + l2tpv3['mtu'] = '1488' - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if l2tpv3['ipv6_autoconf'] or 'dhcpv6' in l2tpv3['address']: - l2tpv3['ipv6_accept_ra'] = 2 + # To delete an l2tpv3 interface we need the current tunnel and session-id + if 'deleted' in l2tpv3: + tmp = leaf_node_changed(conf, ['tunnel-id']) + l2tpv3.update({'tunnel_id': tmp}) - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - l2tpv3['mtu'] = int(conf.return_value('mtu')) - - # Remote session id - if conf.exists('peer-session-id'): - l2tpv3['peer_session_id'] = conf.return_value('peer-session-id') - - # Remote tunnel id - if conf.exists('peer-tunnel-id'): - l2tpv3['peer_tunnel_id'] = conf.return_value('peer-tunnel-id') - - # Remote address of L2TPv3 tunnel - if conf.exists('remote-ip'): - l2tpv3['remote_address'] = conf.return_value('remote-ip') - - # Local session id - if conf.exists('session-id'): - l2tpv3['session_id'] = conf.return_value('session-id') - - # get local tunnel port - if conf.exists('source-port'): - l2tpv3['local_port'] = conf.return_value('source-port') - - # get local tunnel id - if conf.exists('tunnel-id'): - l2tpv3['tunnel_id'] = conf.return_value('tunnel-id') + tmp = leaf_node_changed(conf, ['session-id']) + l2tpv3.update({'session_id': tmp}) return l2tpv3 - def verify(l2tpv3): - interface = l2tpv3['intf'] - - if l2tpv3['deleted']: - if l2tpv3['is_bridge_member']: - raise ConfigError(( - f'Interface "{l2tpv3["intf"]}" cannot be deleted as it is a ' - f'member of bridge "{l2tpv3["is_bridge_member"]}"!')) - + if 'deleted' in l2tpv3: + verify_bridge_delete(l2tpv3) return None - if not l2tpv3['local_address']: - raise ConfigError(f'Must configure the l2tpv3 local-ip for {interface}') - - if not is_addr_assigned(l2tpv3['local_address']): - raise ConfigError(f'Must use a configured IP on l2tpv3 local-ip for {interface}') + interface = l2tpv3['ifname'] - if not l2tpv3['remote_address']: - raise ConfigError(f'Must configure the l2tpv3 remote-ip for {interface}') + for key in ['local_ip', 'remote_ip', 'tunnel_id', 'peer_tunnel_id', + 'session_id', 'peer_session_id']: + if key not in l2tpv3: + tmp = key.replace('_', '-') + raise ConfigError(f'L2TPv3 {tmp} must be configured!') - if not l2tpv3['tunnel_id']: - raise ConfigError(f'Must configure the l2tpv3 tunnel-id for {interface}') - - if not l2tpv3['peer_tunnel_id']: - raise ConfigError(f'Must configure the l2tpv3 peer-tunnel-id for {interface}') - - if not l2tpv3['session_id']: - raise ConfigError(f'Must configure the l2tpv3 session-id for {interface}') - - if not l2tpv3['peer_session_id']: - raise ConfigError(f'Must configure the l2tpv3 peer-session-id for {interface}') - - if ( l2tpv3['is_bridge_member'] - and ( l2tpv3['address'] - or l2tpv3['ipv6_eui64_prefix'] - or l2tpv3['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{l2tpv3["intf"]}" ' - f'as it is a member of bridge "{l2tpv3["is_bridge_member"]}"!')) + if not is_addr_assigned(l2tpv3['local_ip']): + raise ConfigError('L2TPv3 local-ip address ' + '"{local_ip}" is not configured!'.format(**l2tpv3)) + verify_address(l2tpv3) return None - def generate(l2tpv3): return None @@ -225,65 +90,34 @@ def apply(l2tpv3): conf = deepcopy(L2TPv3If.get_config()) # Check if L2TPv3 interface already exists - if l2tpv3['intf'] in interfaces(): + if l2tpv3['ifname'] in interfaces(): # L2TPv3 is picky when changing tunnels/sessions, thus we can simply # always delete it first. conf['session_id'] = l2tpv3['session_id'] conf['tunnel_id'] = l2tpv3['tunnel_id'] - l = L2TPv3If(l2tpv3['intf'], **conf) + l = L2TPv3If(l2tpv3['ifname'], **conf) l.remove() - if not l2tpv3['deleted']: + if 'deleted' not in l2tpv3: conf['peer_tunnel_id'] = l2tpv3['peer_tunnel_id'] - conf['local_port'] = l2tpv3['local_port'] - conf['remote_port'] = l2tpv3['remote_port'] + conf['local_port'] = l2tpv3['source_port'] + conf['remote_port'] = l2tpv3['destination_port'] conf['encapsulation'] = l2tpv3['encapsulation'] - conf['local_address'] = l2tpv3['local_address'] - conf['remote_address'] = l2tpv3['remote_address'] + conf['local_address'] = l2tpv3['local_ip'] + conf['remote_address'] = l2tpv3['remote_ip'] conf['session_id'] = l2tpv3['session_id'] conf['tunnel_id'] = l2tpv3['tunnel_id'] conf['peer_session_id'] = l2tpv3['peer_session_id'] # Finally create the new interface - l = L2TPv3If(l2tpv3['intf'], **conf) - # update interface description used e.g. by SNMP - l.set_alias(l2tpv3['description']) - # Maximum Transfer Unit (MTU) - l.set_mtu(l2tpv3['mtu']) - # IPv6 accept RA - l.set_ipv6_accept_ra(l2tpv3['ipv6_accept_ra']) - # IPv6 address autoconfiguration - l.set_ipv6_autoconf(l2tpv3['ipv6_autoconf']) - # IPv6 forwarding - l.set_ipv6_forwarding(l2tpv3['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - l.set_ipv6_dad_messages(l2tpv3['ipv6_dup_addr_detect']) - - # Configure interface address(es) - no need to implicitly delete the - # old addresses as they have already been removed by deleting the - # interface above - for addr in l2tpv3['address']: - l.add_addr(addr) - - # IPv6 EUI-based addresses - for addr in l2tpv3['ipv6_eui64_prefix']: - l.add_ipv6_eui64_address(addr) - - # As the interface is always disabled first when changing parameters - # we will only re-enable the interface if it is not administratively - # disabled - if not l2tpv3['disable']: - l.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if l2tpv3['is_bridge_member']: - l.add_to_bridge(l2tpv3['is_bridge_member']) + l = L2TPv3If(l2tpv3['ifname'], **conf) + l.update(l2tpv3) return None if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 2368f88a9..0398cd591 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -18,31 +18,21 @@ import os from sys import exit -from vyos.ifconfig import LoopbackIf from vyos.config import Config -from vyos import ConfigError, airbag +from vyos.configdict import get_interface_dict +from vyos.ifconfig import LoopbackIf +from vyos import ConfigError +from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = ['interfaces', 'loopback', ifname] - - loopback = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if loopback == {}: - loopback.update({'deleted' : ''}) - - # store interface instance name in dictionary - loopback.update({'ifname': ifname}) - + base = ['interfaces', 'loopback'] + 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 56273f71a..ca15212d4 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -20,16 +20,14 @@ from copy import deepcopy from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.ifconfig import MACsecIf from vyos.template import render from vyos.util import call -from vyos.validate import is_member from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -38,50 +36,25 @@ airbag.enable() wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - # retrieve interface default values base = ['interfaces', 'macsec'] - default_values = defaults(base) + macsec = get_interface_dict(conf, base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - - macsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # Check if interface has been removed - if macsec == {}: - tmp = { - 'deleted' : '', - 'source_interface' : conf.return_effective_value( + if 'deleted' in macsec: + source_interface = conf.return_effective_value( base + ['source-interface']) - } - macsec.update(tmp) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - macsec = dict_merge(default_values, macsec) - - # Add interface instance name into dictionary - macsec.update({'ifname': ifname}) - - # Check if we are a member of any bridge - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - macsec.update(tmp) + macsec.update({'source_interface': source_interface}) return macsec def verify(macsec): - if 'deleted' in macsec.keys(): + if 'deleted' in macsec: verify_bridge_delete(macsec) return None @@ -89,18 +62,18 @@ def verify(macsec): verify_vrf(macsec) verify_address(macsec) - if not (('security' in macsec.keys()) and - ('cipher' in macsec['security'].keys())): + if not (('security' in macsec) and + ('cipher' in macsec['security'])): raise ConfigError( 'Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) - if (('security' in macsec.keys()) and - ('encrypt' in macsec['security'].keys())): + if (('security' in macsec) and + ('encrypt' in macsec['security'])): tmp = macsec.get('security') - if not (('mka' in tmp.keys()) and - ('cak' in tmp['mka'].keys()) and - ('ckn' in tmp['mka'].keys())): + if not (('mka' in tmp) and + ('cak' in tmp['mka']) and + ('ckn' in tmp['mka'])): raise ConfigError('Missing mandatory MACsec security ' 'keys as encryption is enabled!') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 3ee57e83c..b9a88a949 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -22,51 +22,34 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.configverify import verify_source_interface from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - # retrieve interface default values base = ['interfaces', 'pppoe'] - default_values = defaults(base) - # PPPoE is "special" the default MTU is 1492 - update accordingly - default_values['mtu'] = '1492' - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] + pppoe = get_interface_dict(conf, base) - pppoe = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if pppoe == {}: - pppoe.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - pppoe = dict_merge(default_values, pppoe) - - # Add interface instance name into dictionary - pppoe.update({'ifname': ifname}) + # PPPoE is "special" the default MTU is 1492 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + pppoe['mtu'] = '1492' return pppoe def verify(pppoe): - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # bail out early return None @@ -92,7 +75,7 @@ def generate(pppoe): config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c] - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # stop DHCPv6-PD client call(f'systemctl stop dhcp6c@{ifname}.service') # Hang-up PPPoE connection @@ -130,11 +113,11 @@ def generate(pppoe): return None def apply(pppoe): - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # bail out early return None - if 'disable' not in pppoe.keys(): + if 'disable' not in pppoe: # Dial PPPoE connection call('systemctl restart ppp@{ifname}.service'.format(**pppoe)) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index fb8237bee..4afea2b3a 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -18,115 +18,44 @@ import os from copy import deepcopy from sys import exit -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.ifconfig import MACVLANIf, Section -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config +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_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.ifconfig import MACVLANIf from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'source_interface': '', - 'recreating_required': False, - 'mode': 'private', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], - 'vrf': '' -} - def get_config(): - peth = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'pseudo-ethernet'] + peth = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - peth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if interface has been removed - cfg_base = ['interfaces', 'pseudo-ethernet', peth['intf']] - if not conf.exists(cfg_base): - peth['deleted'] = True - return peth - - # set new configuration level - conf.set_level(cfg_base) - - peth, disabled = intf_to_dict(conf, default_config_data) - - # ARP cache entry timeout in seconds - if conf.exists(['ip', 'arp-cache-timeout']): - peth['ip_arp_cache_tmo'] = int(conf.return_value(['ip', 'arp-cache-timeout'])) - - # Enable private VLAN proxy ARP on this interface - if conf.exists(['ip', 'proxy-arp-pvlan']): - peth['ip_proxy_arp_pvlan'] = 1 - - # Physical interface - if conf.exists(['source-interface']): - peth['source_interface'] = conf.return_value(['source-interface']) - tmp = conf.return_effective_value(['source-interface']) - if tmp != peth['source_interface']: - peth['recreating_required'] = True - - # MACvlan mode - if conf.exists(['mode']): - peth['mode'] = conf.return_value(['mode']) - tmp = conf.return_effective_value(['mode']) - if tmp != peth['mode']: - peth['recreating_required'] = True - - add_to_dict(conf, disabled, peth, 'vif', 'vif') - add_to_dict(conf, disabled, peth, 'vif-s', 'vif_s') + mode = leaf_node_changed(conf, ['mode']) + if mode: + peth.update({'mode_old' : mode}) return peth def verify(peth): - if peth['deleted']: - if peth['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{peth["intf"]}" as it is a ' - f'member of bridge "{peth["is_bridge_member"]}"!')) - + if 'deleted' in peth: + verify_bridge_delete(peth) return None - if not peth['source_interface']: - raise ConfigError(( - f'Link device must be set for pseudo-ethernet "{peth["intf"]}"')) - - if not peth['source_interface'] in interfaces(): - raise ConfigError(( - f'Pseudo-ethernet "{peth["intf"]}" link device does not exist')) - - if ( peth['is_bridge_member'] - and ( peth['address'] - or peth['ipv6_eui64_prefix'] - or peth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{peth["intf"]}" ' - f'as it is a member of bridge "{peth["is_bridge_member"]}"!')) - - if peth['vrf']: - if peth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{peth["vrf"]}" does not exist') - - if peth['is_bridge_member']: - raise ConfigError(( - f'Interface "{peth["intf"]}" cannot be member of VRF ' - f'"{peth["vrf"]}" and bridge {peth["is_bridge_member"]} ' - f'at the same time!')) + verify_source_interface(peth) + verify_vrf(peth) + verify_address(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) @@ -136,17 +65,16 @@ def generate(peth): return None def apply(peth): - if peth['deleted']: + if 'deleted' in peth: # delete interface - MACVLANIf(peth['intf']).remove() + MACVLANIf(peth['ifname']).remove() return None # Check if MACVLAN interface already exists. Parameters like the underlaying # source-interface device or mode can not be changed on the fly and the interface # needs to be recreated from the bottom. - if peth['intf'] in interfaces(): - if peth['recreating_required']: - MACVLANIf(peth['intf']).remove() + if 'mode_old' in peth: + MACVLANIf(peth['ifname']).remove() # MACVLAN interface needs to be created on-block instead of passing a ton # of arguments, I just use a dict that is managed by vyos.ifconfig @@ -158,98 +86,8 @@ def apply(peth): # It is safe to "re-create" the interface always, there is a sanity check # that the interface will only be create if its non existent - p = MACVLANIf(peth['intf'], **conf) - - # update interface description used e.g. within SNMP - p.set_alias(peth['description']) - - if peth['dhcp_client_id']: - p.dhcp.v4.options['client_id'] = peth['dhcp_client_id'] - - if peth['dhcp_hostname']: - p.dhcp.v4.options['hostname'] = peth['dhcp_hostname'] - - if peth['dhcp_vendor_class_id']: - p.dhcp.v4.options['vendor_class_id'] = peth['dhcp_vendor_class_id'] - - if peth['dhcpv6_prm_only']: - p.dhcp.v6.options['dhcpv6_prm_only'] = True - - if peth['dhcpv6_temporary']: - p.dhcp.v6.options['dhcpv6_temporary'] = True - - if peth['dhcpv6_pd_length']: - p.dhcp.v6.options['dhcpv6_pd_length'] = peth['dhcpv6_pd_length'] - - if peth['dhcpv6_pd_interfaces']: - p.dhcp.v6.options['dhcpv6_pd_interfaces'] = peth['dhcpv6_pd_interfaces'] - - # ignore link state changes - p.set_link_detect(peth['disable_link_detect']) - # configure ARP cache timeout in milliseconds - p.set_arp_cache_tmo(peth['ip_arp_cache_tmo']) - # configure ARP filter configuration - p.set_arp_filter(peth['ip_disable_arp_filter']) - # configure ARP accept - p.set_arp_accept(peth['ip_enable_arp_accept']) - # configure ARP announce - p.set_arp_announce(peth['ip_enable_arp_announce']) - # configure ARP ignore - p.set_arp_ignore(peth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - p.set_proxy_arp(peth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - p.set_proxy_arp_pvlan(peth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - p.set_ipv6_accept_ra(peth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - p.set_ipv6_autoconf(peth['ipv6_autoconf']) - # IPv6 forwarding - p.set_ipv6_forwarding(peth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - p.set_ipv6_dad_messages(peth['ipv6_dup_addr_detect']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not peth['is_bridge_member']: - p.set_vrf(peth['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in peth['ipv6_eui64_prefix_remove']: - p.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if peth['mac']: - p.set_mac(peth['mac']) - - # Add IPv6 EUI-based addresses - for addr in peth['ipv6_eui64_prefix']: - p.add_ipv6_eui64_address(addr) - - # Change interface mode - p.set_mode(peth['mode']) - - # Enable/Disable interface - if peth['disable']: - p.set_admin_state('down') - else: - p.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in peth['address_remove']: - p.del_addr(addr) - for addr in peth['address']: - p.add_addr(addr) - - # re-add ourselves to any bridge we might have fallen out of - if peth['is_bridge_member']: - p.add_to_bridge(peth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(p, peth) - + p = MACVLANIf(peth['ifname'], **conf) + p.update(peth) return None if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index c24c9a7ce..982aefa5f 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -25,6 +25,7 @@ from vyos.config import Config from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf from vyos.util import chown, chmod_750, call +from vyos.util import check_kmod from vyos.validate import is_member, is_ipv6 from vyos import ConfigError @@ -32,6 +33,7 @@ from vyos import airbag airbag.enable() kdir = r'/config/auth/wireguard' +k_mod = 'wireguard' default_config_data = { 'intfc': '', @@ -50,14 +52,6 @@ default_config_data = { 'vrf': '' } -def _check_kmod(): - modules = ['wireguard'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def _migrate_default_keys(): if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' @@ -315,7 +309,7 @@ def apply(wg): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) _migrate_default_keys() c = get_config() verify(c) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 0162b642c..b6f247952 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -15,497 +15,163 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os + from sys import exit from re import findall - from copy import deepcopy - -from netifaces import interfaces from netaddr import EUI, mac_unix_expanded from vyos.config import Config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.ifconfig import WiFiIf, Section -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config +from vyos.configdict import get_interface_dict +from vyos.configdict import dict_merge +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_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import WiFiIf from vyos.template import render -from vyos.util import chown, call -from vyos.validate import is_member +from vyos.util import call from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'cap_ht' : False, - 'cap_ht_40mhz_incapable' : False, - 'cap_ht_powersave' : False, - 'cap_ht_chan_set_width' : '', - 'cap_ht_delayed_block_ack' : False, - 'cap_ht_dsss_cck_40' : False, - 'cap_ht_greenfield' : False, - 'cap_ht_ldpc' : False, - 'cap_ht_lsig_protection' : False, - 'cap_ht_max_amsdu' : '', - 'cap_ht_short_gi' : [], - 'cap_ht_smps' : '', - 'cap_ht_stbc_rx' : '', - 'cap_ht_stbc_tx' : False, - 'cap_req_ht' : False, - 'cap_req_vht' : False, - 'cap_vht' : False, - 'cap_vht_antenna_cnt' : '', - 'cap_vht_antenna_fixed' : False, - 'cap_vht_beamform' : '', - 'cap_vht_center_freq_1' : '', - 'cap_vht_center_freq_2' : '', - 'cap_vht_chan_set_width' : '', - 'cap_vht_ldpc' : False, - 'cap_vht_link_adaptation' : '', - 'cap_vht_max_mpdu_exp' : '', - 'cap_vht_max_mpdu' : '', - 'cap_vht_short_gi' : [], - 'cap_vht_stbc_rx' : '', - 'cap_vht_stbc_tx' : False, - 'cap_vht_tx_powersave' : False, - 'cap_vht_vht_cf' : False, - 'channel': '', - 'country_code': '', - 'deleted': False, - 'disable_broadcast_ssid' : False, - 'disable_link_detect' : 1, - 'expunge_failing_stations' : False, - 'hw_id' : '', - 'intf': '', - 'isolate_stations' : False, - 'max_stations' : '', - 'mgmt_frame_protection' : 'disabled', - 'mode' : 'g', - 'phy' : '', - 'reduce_transmit_power' : '', - 'sec_wep' : False, - 'sec_wep_key' : [], - 'sec_wpa' : False, - 'sec_wpa_cipher' : [], - 'sec_wpa_mode' : 'both', - 'sec_wpa_passphrase' : '', - 'sec_wpa_radius' : [], - 'ssid' : '', - 'op_mode' : 'monitor', - 'vif': {}, - 'vif_remove': [], - 'vif_s': {}, - 'vif_s_remove': [] -} - # XXX: wpa_supplicant works on the source interface -wpa_suppl_conf = '/run/wpa_supplicant/{intf}.conf' -hostapd_conf = '/run/hostapd/{intf}.conf' +wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' +hostapd_conf = '/run/hostapd/{ifname}.conf' + +def find_other_stations(conf, base, ifname): + """ + Only one wireless interface per phy can be in station mode - + find all interfaces attached to a phy which run in station mode + """ + old_level = conf.get_level() + conf.set_level(base) + dict = {} + for phy in os.listdir('/sys/class/ieee80211'): + list = [] + for interface in conf.list_nodes([]): + if interface == ifname: + continue + # the following node is mandatory + if conf.exists([interface, 'physical-device', phy]): + tmp = conf.return_value([interface, 'type']) + if tmp == 'station': + list.append(interface) + if list: + dict.update({phy: list}) + conf.set_level(old_level) + return dict def get_config(): - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # check if wireless interface has been removed - cfg_base = ['interfaces', 'wireless ', ifname] - if not conf.exists(cfg_base): - wifi = deepcopy(default_config_data) - wifi['intf'] = ifname - wifi['deleted'] = True - # we need to know if we're a bridge member so we can refuse deletion - wifi['is_bridge_member'] = is_member(conf, wifi['intf'], 'bridge') - # we can not bail out early as wireless interface can not be removed - # Kernel will complain with: RTNETLINK answers: Operation not supported. - # Thus we need to remove individual settings - return wifi - - # set new configuration level - conf.set_level(cfg_base) - - # get common interface settings - wifi, disabled = intf_to_dict(conf, default_config_data) - - # 40MHz intolerance, use 20MHz only - if conf.exists('capabilities ht 40mhz-incapable'): - wifi['cap_ht'] = True - wifi['cap_ht_40mhz_incapable'] = True - - # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] - if conf.exists('capabilities ht auto-powersave'): - wifi['cap_ht'] = True - wifi['cap_ht_powersave'] = True - - # Supported channel set width - if conf.exists('capabilities ht channel-set-width'): - wifi['cap_ht'] = True - wifi['cap_ht_chan_set_width'] = conf.return_values('capabilities ht channel-set-width') - - # HT-delayed Block Ack - if conf.exists('capabilities ht delayed-block-ack'): - wifi['cap_ht'] = True - wifi['cap_ht_delayed_block_ack'] = True - - # DSSS/CCK Mode in 40 MHz - if conf.exists('capabilities ht dsss-cck-40'): - wifi['cap_ht'] = True - wifi['cap_ht_dsss_cck_40'] = True - - # HT-greenfield capability - if conf.exists('capabilities ht greenfield'): - wifi['cap_ht'] = True - wifi['cap_ht_greenfield'] = True - - # LDPC coding capability - if conf.exists('capabilities ht ldpc'): - wifi['cap_ht'] = True - wifi['cap_ht_ldpc'] = True - - # L-SIG TXOP protection capability - if conf.exists('capabilities ht lsig-protection'): - wifi['cap_ht'] = True - wifi['cap_ht_lsig_protection'] = True - - # Set Maximum A-MSDU length - if conf.exists('capabilities ht max-amsdu'): - wifi['cap_ht'] = True - wifi['cap_ht_max_amsdu'] = conf.return_value('capabilities ht max-amsdu') - - # Short GI capabilities - if conf.exists('capabilities ht short-gi'): - wifi['cap_ht'] = True - wifi['cap_ht_short_gi'] = conf.return_values('capabilities ht short-gi') - - # Spatial Multiplexing Power Save (SMPS) settings - if conf.exists('capabilities ht smps'): - wifi['cap_ht'] = True - wifi['cap_ht_smps'] = conf.return_value('capabilities ht smps') - - # Support for receiving PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities ht stbc rx'): - wifi['cap_ht'] = True - wifi['cap_ht_stbc_rx'] = conf.return_value('capabilities ht stbc rx') - - # Support for sending PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities ht stbc tx'): - wifi['cap_ht'] = True - wifi['cap_ht_stbc_tx'] = True - - # Require stations to support HT PHY (reject association if they do not) - if conf.exists('capabilities require-ht'): - wifi['cap_req_ht'] = True - - # Require stations to support VHT PHY (reject association if they do not) - if conf.exists('capabilities require-vht'): - wifi['cap_req_vht'] = True - - # Number of antennas on this card - if conf.exists('capabilities vht antenna-count'): - wifi['cap_vht'] = True - wifi['cap_vht_antenna_cnt'] = conf.return_value('capabilities vht antenna-count') - - # set if antenna pattern does not change during the lifetime of an association - if conf.exists('capabilities vht antenna-pattern-fixed'): - wifi['cap_vht'] = True - wifi['cap_vht_antenna_fixed'] = True - - # Beamforming capabilities - if conf.exists('capabilities vht beamform'): - wifi['cap_vht'] = True - wifi['cap_vht_beamform'] = conf.return_values('capabilities vht beamform') - - # VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes) - if conf.exists('capabilities vht center-channel-freq freq-1'): - wifi['cap_vht'] = True - wifi['cap_vht_center_freq_1'] = conf.return_value('capabilities vht center-channel-freq freq-1') - - # VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode) - if conf.exists('capabilities vht center-channel-freq freq-2'): - wifi['cap_vht'] = True - wifi['cap_vht_center_freq_2'] = conf.return_value('capabilities vht center-channel-freq freq-2') - - # VHT operating Channel width - if conf.exists('capabilities vht channel-set-width'): - wifi['cap_vht'] = True - wifi['cap_vht_chan_set_width'] = conf.return_value('capabilities vht channel-set-width') - - # LDPC coding capability - if conf.exists('capabilities vht ldpc'): - wifi['cap_vht'] = True - wifi['cap_vht_ldpc'] = True - - # VHT link adaptation capabilities - if conf.exists('capabilities vht link-adaptation'): - wifi['cap_vht'] = True - wifi['cap_vht_link_adaptation'] = conf.return_value('capabilities vht link-adaptation') - - # Set the maximum length of A-MPDU pre-EOF padding that the station can receive - if conf.exists('capabilities vht max-mpdu-exp'): - wifi['cap_vht'] = True - wifi['cap_vht_max_mpdu_exp'] = conf.return_value('capabilities vht max-mpdu-exp') - - # Increase Maximum MPDU length - if conf.exists('capabilities vht max-mpdu'): - wifi['cap_vht'] = True - wifi['cap_vht_max_mpdu'] = conf.return_value('capabilities vht max-mpdu') - - # Increase Maximum MPDU length - if conf.exists('capabilities vht short-gi'): - wifi['cap_vht'] = True - wifi['cap_vht_short_gi'] = conf.return_values('capabilities vht short-gi') - - # Support for receiving PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities vht stbc rx'): - wifi['cap_vht'] = True - wifi['cap_vht_stbc_rx'] = conf.return_value('capabilities vht stbc rx') - - # Support for the transmission of at least 2x1 STBC (Space Time Block Coding) - if conf.exists('capabilities vht stbc tx'): - wifi['cap_vht'] = True - wifi['cap_vht_stbc_tx'] = True - - # Support for VHT TXOP Power Save Mode - if conf.exists('capabilities vht tx-powersave'): - wifi['cap_vht'] = True - wifi['cap_vht_tx_powersave'] = True - - # STA supports receiving a VHT variant HT Control field - if conf.exists('capabilities vht vht-cf'): - wifi['cap_vht'] = True - wifi['cap_vht_vht_cf'] = True - - # Wireless radio channel - if conf.exists('channel'): - wifi['channel'] = conf.return_value('channel') - - # Disable broadcast of SSID from access-point - if conf.exists('disable-broadcast-ssid'): - wifi['disable_broadcast_ssid'] = True - - # Disassociate stations based on excessive transmission failures - if conf.exists('expunge-failing-stations'): - wifi['expunge_failing_stations'] = True - - # retrieve real hardware address - if conf.exists('hw-id'): - wifi['hw_id'] = conf.return_value('hw-id') - - # Isolate stations on the AP so they cannot see each other - if conf.exists('isolate-stations'): - wifi['isolate_stations'] = True - - # Wireless physical device - if conf.exists('physical-device'): - wifi['phy'] = conf.return_value('physical-device') - - # Maximum number of wireless radio stations - if conf.exists('max-stations'): - wifi['max_stations'] = conf.return_value('max-stations') - - # Management Frame Protection (MFP) according to IEEE 802.11w - if conf.exists('mgmt-frame-protection'): - wifi['mgmt_frame_protection'] = conf.return_value('mgmt-frame-protection') - - # Wireless radio mode - if conf.exists('mode'): - wifi['mode'] = conf.return_value('mode') - - # Transmission power reduction in dBm - if conf.exists('reduce-transmit-power'): - wifi['reduce_transmit_power'] = conf.return_value('reduce-transmit-power') - - # WEP enabled? - if conf.exists('security wep'): - wifi['sec_wep'] = True - - # WEP encryption key(s) - if conf.exists('security wep key'): - wifi['sec_wep_key'] = conf.return_values('security wep key') - - # WPA enabled? - if conf.exists('security wpa'): - wifi['sec_wpa'] = True - - # WPA Cipher suite - if conf.exists('security wpa cipher'): - wifi['sec_wpa_cipher'] = conf.return_values('security wpa cipher') - - # WPA mode - if conf.exists('security wpa mode'): - wifi['sec_wpa_mode'] = conf.return_value('security wpa mode') - - # WPA default ciphers depend on WPA mode - if not wifi['sec_wpa_cipher']: - if wifi['sec_wpa_mode'] == 'wpa': - wifi['sec_wpa_cipher'].append('TKIP') - wifi['sec_wpa_cipher'].append('CCMP') - - elif wifi['sec_wpa_mode'] == 'wpa2': - wifi['sec_wpa_cipher'].append('CCMP') - - elif wifi['sec_wpa_mode'] == 'both': - wifi['sec_wpa_cipher'].append('CCMP') - wifi['sec_wpa_cipher'].append('TKIP') - - # WPA Group Cipher suite - if conf.exists('security wpa group-cipher'): - wifi['sec_wpa_group_cipher'] = conf.return_values('security wpa group-cipher') - - # WPA personal shared pass phrase - if conf.exists('security wpa passphrase'): - wifi['sec_wpa_passphrase'] = conf.return_value('security wpa passphrase') - - # WPA RADIUS source address - if conf.exists('security wpa radius source-address'): - wifi['sec_wpa_radius_source'] = conf.return_value('security wpa radius source-address') - - # WPA RADIUS server - for server in conf.list_nodes('security wpa radius server'): - # set new configuration level - conf.set_level(cfg_base + ' security wpa radius server ' + server) - radius = { - 'server' : server, - 'acc_port' : '', - 'disabled': False, - 'port' : 1812, - 'key' : '' - } - - # RADIUS server port - if conf.exists('port'): - radius['port'] = int(conf.return_value('port')) - - # receive RADIUS accounting info - if conf.exists('accounting'): - radius['acc_port'] = radius['port'] + 1 - - # Check if RADIUS server was temporary disabled - if conf.exists(['disable']): - radius['disabled'] = True - - # RADIUS server shared-secret - if conf.exists('key'): - radius['key'] = conf.return_value('key') - - # append RADIUS server to list of servers - wifi['sec_wpa_radius'].append(radius) - - # re-set configuration level to parse new nodes - conf.set_level(cfg_base) - - # Wireless access-point service set identifier (SSID) - if conf.exists('ssid'): - wifi['ssid'] = conf.return_value('ssid') - - # Wireless device type for this interface - if conf.exists('type'): - tmp = conf.return_value('type') - if tmp == 'access-point': - tmp = 'ap' - - wifi['op_mode'] = tmp + base = ['interfaces', 'wireless'] + wifi = get_interface_dict(conf, base) + + if 'security' in wifi and 'wpa' in wifi['security']: + wpa_cipher = wifi['security']['wpa'].get('cipher') + wpa_mode = wifi['security']['wpa'].get('mode') + if not wpa_cipher: + tmp = None + if wpa_mode == 'wpa': + tmp = {'security': {'wpa': {'cipher' : ['TKIP', 'CCMP']}}} + elif wpa_mode == 'wpa2': + tmp = {'security': {'wpa': {'cipher' : ['CCMP']}}} + elif wpa_mode == 'both': + tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'TKIP']}}} + + if tmp: wifi = dict_merge(tmp, wifi) # retrieve configured regulatory domain - conf.set_level('system') - if conf.exists('wifi-regulatory-domain'): - wifi['country_code'] = conf.return_value('wifi-regulatory-domain') + conf.set_level(['system']) + if conf.exists(['wifi-regulatory-domain']): + wifi['country_code'] = conf.return_value(['wifi-regulatory-domain']) - return wifi + # Only one wireless interface per phy can be in station mode + tmp = find_other_stations(conf, base, wifi['ifname']) + if tmp: wifi['station_interfaces'] = tmp + return wifi def verify(wifi): - if wifi['deleted']: - if wifi['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{wifi["intf"]}" as it is a ' - f'member of bridge "{wifi["is_bridge_member"]}"!')) - + if 'deleted' in wifi: + verify_bridge_delete(wifi) return None - if wifi['op_mode'] != 'monitor' and not wifi['ssid']: - raise ConfigError('SSID must be set for {}'.format(wifi['intf'])) + if 'physical_device' not in wifi: + raise ConfigError('You must specify a physical-device "phy"') - if not wifi['phy']: - raise ConfigError('You must specify physical-device') - - if not wifi['mode']: + if 'type' not in wifi: raise ConfigError('You must specify a WiFi mode') - if wifi['op_mode'] == 'ap': - c = Config() - if not c.exists('system wifi-regulatory-domain'): - raise ConfigError('Wireless regulatory domain is mandatory,\n' \ - 'use "set system wifi-regulatory-domain".') - - if not wifi['channel']: - raise ConfigError('Channel must be set for {}'.format(wifi['intf'])) - - if len(wifi['sec_wep_key']) > 4: - raise ConfigError('No more then 4 WEP keys configurable') + if 'ssid' not in wifi and wifi['type'] != 'monitor': + raise ConfigError('SSID must be configured') - if wifi['cap_vht'] and not wifi['cap_ht']: - raise ConfigError('Specify HT flags if you want to use VHT!') - - if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1: - raise ConfigError('Cannot use beam forming with just one antenna!') - - if wifi['cap_vht_beamform'] == 'single-user-beamformer' and wifi['cap_vht_antenna_cnt'] < 3: - # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 - raise ConfigError('Single-user beam former requires at least 3 antennas!') - - if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0): - raise ConfigError('Missing WEP keys') - - if wifi['sec_wpa'] and not (wifi['sec_wpa_passphrase'] or wifi['sec_wpa_radius']): - raise ConfigError('Misssing WPA key or RADIUS server') - - for radius in wifi['sec_wpa_radius']: - if not radius['key']: - raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) - - if ( wifi['is_bridge_member'] - and ( wifi['address'] - or wifi['ipv6_eui64_prefix'] - or wifi['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{wifi["intf"]}" ' - f'as it is a member of bridge "{wifi["is_bridge_member"]}"!')) - - if wifi['vrf']: - if wifi['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{wifi["vrf"]}" does not exist') - - if wifi['is_bridge_member']: - raise ConfigError(( - f'Interface "{wifi["intf"]}" cannot be member of VRF ' - f'"{wifi["vrf"]}" and bridge {wifi["is_bridge_member"]} ' - f'at the same time!')) + if wifi['type'] == 'access-point': + if 'country_code' not in wifi: + raise ConfigError('Wireless regulatory domain is mandatory,\n' \ + 'use "set system wifi-regulatory-domain" for configuration.') + + if 'channel' not in wifi: + raise ConfigError('Wireless channel must be configured!') + + if 'security' in wifi: + if {'wep', 'wpa'} <= set(wifi.get('security', {})): + raise ConfigError('Must either use WEP or WPA security!') + + if 'wep' in wifi['security']: + if 'key' in wifi['security']['wep'] and len(wifi['security']['wep']) > 4: + raise ConfigError('No more then 4 WEP keys configurable') + elif 'key' not in wifi['security']['wep']: + raise ConfigError('Security WEP configured - missing WEP keys!') + + elif 'wpa' in wifi['security']: + wpa = wifi['security']['wpa'] + if not any(i in ['passphrase', 'radius'] for i in wpa): + raise ConfigError('Misssing WPA key or RADIUS server') + + if 'radius' in wpa: + if 'server' in wpa['radius']: + for server in wpa['radius']['server']: + if 'key' not in wpa['radius']['server'][server]: + raise ConfigError(f'Misssing RADIUS shared secret key for server: {server}') + + if 'capabilities' in wifi: + capabilities = wifi['capabilities'] + if 'vht' in capabilities: + if 'ht' not in capabilities: + raise ConfigError('Specify HT flags if you want to use VHT!') + + if {'beamform', 'antenna_count'} <= set(capabilities.get('vht', {})): + if capabilities['vht']['antenna_count'] == '1': + raise ConfigError('Cannot use beam forming with just one antenna!') + + if capabilities['vht']['beamform'] == 'single-user-beamformer': + if int(capabilities['vht']['antenna_count']) < 3: + # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 + raise ConfigError('Single-user beam former requires at least 3 antennas!') + + if 'station_interfaces' in wifi and wifi['type'] == 'station': + phy = wifi['physical_device'] + if phy in wifi['station_interfaces']: + if len(wifi['station_interfaces'][phy]) > 0: + raise ConfigError('Only one station per wireless physical interface possible!') + + verify_address(wifi) + verify_vrf(wifi) # use common function to verify VLAN configuration verify_vlan_config(wifi) - conf = Config() - # Only one wireless interface per phy can be in station mode - base = ['interfaces', 'wireless'] - for phy in os.listdir('/sys/class/ieee80211'): - stations = [] - for wlan in conf.list_nodes(base): - # the following node is mandatory - if conf.exists(base + [wlan, 'physical-device', phy]): - tmp = conf.return_value(base + [wlan, 'type']) - if tmp == 'station': - stations.append(wlan) - - if len(stations) > 1: - raise ConfigError('Only one station per wireless physical interface possible!') - return None def generate(wifi): - interface = wifi['intf'] + interface = wifi['ifname'] # always stop hostapd service first before reconfiguring it call(f'systemctl stop hostapd@{interface}.service') @@ -513,7 +179,7 @@ def generate(wifi): call(f'systemctl stop wpa_supplicant@{interface}.service') # Delete config files if interface is removed - if wifi['deleted']: + if 'deleted' in wifi: if os.path.isfile(hostapd_conf.format(**wifi)): os.unlink(hostapd_conf.format(**wifi)) @@ -522,10 +188,10 @@ def generate(wifi): return None - if not wifi['mac']: + if 'mac' not in wifi: # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd # generate locally administered MAC address from used phy interface - with open('/sys/class/ieee80211/{}/addresses'.format(wifi['phy']), 'r') as f: + with open('/sys/class/ieee80211/{physical_device}/addresses'.format(**wifi), 'r') as f: # some PHYs tend to have multiple interfaces and thus supply multiple MAC # addresses - we only need the first one for our calculation tmp = f.readline().rstrip() @@ -545,20 +211,18 @@ def generate(wifi): wifi['mac'] = str(mac) # render appropriate new config files depending on access-point or station mode - if wifi['op_mode'] == 'ap': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi) + if wifi['type'] == 'access-point': + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True) - elif wifi['op_mode'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi) + elif wifi['type'] == 'station': + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi, trim_blocks=True) return None def apply(wifi): - interface = wifi['intf'] - if wifi['deleted']: - w = WiFiIf(interface) - # delete interface - w.remove() + interface = wifi['ifname'] + if 'deleted' in wifi: + WiFiIf(interface).remove() else: # WiFi interface needs to be created on-block (e.g. mode or physical # interface) instead of passing a ton of arguments, I just use a dict @@ -566,97 +230,21 @@ def apply(wifi): conf = deepcopy(WiFiIf.get_config()) # Assign WiFi instance configuration parameters to config dict - conf['phy'] = wifi['phy'] + conf['phy'] = wifi['physical_device'] # Finally create the new interface w = WiFiIf(interface, **conf) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not wifi['is_bridge_member']: - w.set_vrf(wifi['vrf']) - - # update interface description used e.g. within SNMP - w.set_alias(wifi['description']) - - if wifi['dhcp_client_id']: - w.dhcp.v4.options['client_id'] = wifi['dhcp_client_id'] - - if wifi['dhcp_hostname']: - w.dhcp.v4.options['hostname'] = wifi['dhcp_hostname'] - - if wifi['dhcp_vendor_class_id']: - w.dhcp.v4.options['vendor_class_id'] = wifi['dhcp_vendor_class_id'] - - if wifi['dhcpv6_prm_only']: - w.dhcp.v6.options['dhcpv6_prm_only'] = True - - if wifi['dhcpv6_temporary']: - w.dhcp.v6.options['dhcpv6_temporary'] = True - - if wifi['dhcpv6_pd_length']: - w.dhcp.v6.options['dhcpv6_pd_length'] = wifi['dhcpv6_pd_length'] - - if wifi['dhcpv6_pd_interfaces']: - w.dhcp.v6.options['dhcpv6_pd_interfaces'] = wifi['dhcpv6_pd_interfaces'] - - # ignore link state changes - w.set_link_detect(wifi['disable_link_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in wifi['ipv6_eui64_prefix_remove']: - w.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed - if wifi['mac']: - w.set_mac(wifi['mac']) - elif wifi['hw_id']: - w.set_mac(wifi['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in wifi['ipv6_eui64_prefix']: - w.add_ipv6_eui64_address(addr) - - # configure ARP filter configuration - w.set_arp_filter(wifi['ip_disable_arp_filter']) - # configure ARP accept - w.set_arp_accept(wifi['ip_enable_arp_accept']) - # configure ARP announce - w.set_arp_announce(wifi['ip_enable_arp_announce']) - # configure ARP ignore - w.set_arp_ignore(wifi['ip_enable_arp_ignore']) - # IPv6 accept RA - w.set_ipv6_accept_ra(wifi['ipv6_accept_ra']) - # IPv6 address autoconfiguration - w.set_ipv6_autoconf(wifi['ipv6_autoconf']) - # IPv6 forwarding - w.set_ipv6_forwarding(wifi['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - w.set_ipv6_dad_messages(wifi['ipv6_dup_addr_detect']) - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in wifi['address_remove']: - w.del_addr(addr) - for addr in wifi['address']: - w.add_addr(addr) - - # apply all vlans to interface - apply_all_vlans(w, wifi) + w.update(wifi) # Enable/Disable interface - interface is always placed in # administrative down state in WiFiIf class - if not wifi['disable']: - w.set_admin_state('up') - + if 'disable' not in wifi: # Physical interface is now configured. Proceed by starting hostapd or # wpa_supplicant daemon. When type is monitor we can just skip this. - if wifi['op_mode'] == 'ap': + if wifi['type'] == 'access-point': call(f'systemctl start hostapd@{interface}.service') - elif wifi['op_mode'] == 'station': + elif wifi['type'] == 'station': call(f'systemctl start wpa_supplicant@{interface}.service') return None diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index ec5a85e54..4081be3c9 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -20,21 +20,16 @@ from fnmatch import fnmatch from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call -from vyos.xml import defaults +from vyos.util import check_kmod from vyos import ConfigError from vyos import airbag airbag.enable() -def check_kmod(): - modules = ['option', 'usb_wwan', 'usbserial'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') +k_mod = ['option', 'usb_wwan', 'usbserial'] def find_device_file(device): """ Recurively search /dev for the given device file and return its full path. @@ -47,44 +42,23 @@ def find_device_file(device): return None def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - # retrieve interface default values base = ['interfaces', 'wirelessmodem'] - default_values = defaults(base) - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - - wwan = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if wwan == {}: - wwan.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - wwan = dict_merge(default_values, wwan) - - # Add interface instance name into dictionary - wwan.update({'ifname': ifname}) - + wwan = get_interface_dict(conf, base) return wwan def verify(wwan): - if 'deleted' in wwan.keys(): + if 'deleted' in wwan: return None - if not 'apn' in wwan.keys(): + if not 'apn' in wwan: raise ConfigError('No APN configured for "{ifname}"'.format(**wwan)) - if not 'device' in wwan.keys(): + if not 'device' in wwan: raise ConfigError('Physical "device" must be configured') # we can not use isfile() here as Linux device files are no regular files @@ -141,11 +115,11 @@ def generate(wwan): return None def apply(wwan): - if 'deleted' in wwan.keys(): + if 'deleted' in wwan: # bail out early return None - if not 'disable' in wwan.keys(): + if not 'disable' in wwan: # "dial" WWAN connection call('systemctl start ppp@{ifname}.service'.format(**wwan)) @@ -153,7 +127,7 @@ def apply(wwan): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 2299717a8..dd34dfd66 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -24,13 +24,17 @@ from netifaces import interfaces from vyos.config import Config from vyos.template import render -from vyos.util import call, cmd +from vyos.util import call +from vyos.util import cmd +from vyos.util import check_kmod from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() +k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] + default_config_data = { 'deleted': False, 'destination': [], @@ -44,15 +48,6 @@ default_config_data = { iptables_nat_config = '/tmp/vyos-nat-rules.nft' -def _check_kmod(): - """ load required Kernel modules """ - modules = ['nft_nat', 'nft_chain_nat_ipv4'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. Handler is required when adding NAT/Conntrack helper targets """ @@ -269,7 +264,7 @@ def apply(nat): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index 15bf63e81..e08bc983a 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -21,22 +21,17 @@ import shutil import syslog as sl import re +from vyos.config import Config from vyos.ifconfig import WireGuardIf - +from vyos.util import cmd +from vyos.util import run +from vyos.util import check_kmod from vyos import ConfigError -from vyos.config import Config -from vyos.util import cmd, run dir = r'/config/auth/wireguard' psk = dir + '/preshared.key' -def check_kmod(): - """ check if kmod is loaded, if not load it """ - if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") - if run('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") - raise ConfigError("modprobe wireguard failed") +k_mod = 'wireguard' def generate_keypair(pk, pub): """ generates a keypair which is stored in /config/auth/wireguard """ @@ -106,7 +101,7 @@ def del_key_dir(kname): if __name__ == '__main__': - check_kmod() + check_kmod(k_mod) parser = argparse.ArgumentParser(description='wireguard key management') parser.add_argument( '--genkey', action="store_true", help='generate key-pair') |