diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configdict.py | 49 | ||||
-rw-r--r-- | python/vyos/configverify.py | 64 | ||||
-rw-r--r-- | python/vyos/ifconfig/bond.py | 8 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 77 | ||||
-rw-r--r-- | python/vyos/ifconfig/ethernet.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 40 | ||||
-rw-r--r-- | python/vyos/ifconfig/l2tpv3.py | 25 | ||||
-rw-r--r-- | python/vyos/ifconfig/tunnel.py | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig/vtun.py | 44 | ||||
-rw-r--r-- | python/vyos/ifconfig/wireguard.py | 2 | ||||
-rw-r--r-- | python/vyos/template.py | 96 | ||||
-rw-r--r-- | python/vyos/util.py | 73 | ||||
-rw-r--r-- | python/vyos/validate.py | 79 |
13 files changed, 354 insertions, 217 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 62df3334c..b14f96364 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -18,7 +18,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ import os -from vyos.util import vyos_dict_search +from vyos.util import dict_search from vyos.xml import defaults from vyos import ConfigError @@ -174,10 +174,10 @@ def T2665_set_dhcpv6pd_defaults(config_dict): pd_defaults = defaults(['interfaces', 'ethernet', 'dhcpv6-options', 'pd']) # Implant default dictionary for DHCPv6-PD instances - if vyos_dict_search('dhcpv6_options.pd.length', config_dict): + if dict_search('dhcpv6_options.pd.length', config_dict): del config_dict['dhcpv6_options']['pd']['length'] - for pd in (vyos_dict_search('dhcpv6_options.pd', config_dict) or []): + for pd in (dict_search('dhcpv6_options.pd', config_dict) or []): config_dict['dhcpv6_options']['pd'][pd] = dict_merge(pd_defaults, config_dict['dhcpv6_options']['pd'][pd]) @@ -219,6 +219,28 @@ def is_member(conf, interface, intftype=None): old_level = conf.set_level(old_level) return ret_val +def has_vlan_subinterface_configured(conf, intf): + """ + Checks if interface has an VLAN subinterface configured. + Checks the following config nodes: + 'vif', 'vif-s' + + Returns True if interface has VLAN subinterface configured, False if it doesn't. + """ + from vyos.ifconfig import Section + ret = False + + old_level = conf.get_level() + conf.set_level([]) + + intfpath = 'interfaces ' + Section.get_config_path(intf) + if ( conf.exists(f'{intfpath} vif') or + conf.exists(f'{intfpath} vif-s')): + ret = True + + conf.set_level(old_level) + return ret + def is_source_interface(conf, interface, intftype=None): """ Checks if passed interface is configured as source-interface of other @@ -332,7 +354,7 @@ def get_interface_dict(config, base, ifname=''): eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) if eui64: - tmp = vyos_dict_search('ipv6.address', dict) + tmp = dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) else: @@ -409,7 +431,7 @@ def get_accel_dict(config, base, chap_secrets): Return a dictionary with the necessary interface config keys. """ from vyos.util import get_half_cpus - from vyos.validate import is_ipv4 + from vyos.template import is_ipv4 dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) @@ -419,12 +441,12 @@ def get_accel_dict(config, base, chap_secrets): # defaults include RADIUS server specifics per TAG node which need to be # added to individual RADIUS servers instead - so we can simply delete them - if vyos_dict_search('authentication.radius.server', default_values): + if dict_search('authentication.radius.server', default_values): del default_values['authentication']['radius']['server'] # defaults include static-ip address per TAG node which need to be added to # individual local users instead - so we can simply delete them - if vyos_dict_search('authentication.local_users.username', default_values): + if dict_search('authentication.local_users.username', default_values): del default_values['authentication']['local_users']['username'] dict = dict_merge(default_values, dict) @@ -448,18 +470,23 @@ def get_accel_dict(config, base, chap_secrets): del dict['name_server'] # Add individual RADIUS server default values - if vyos_dict_search('authentication.radius.server', dict): + if dict_search('authentication.radius.server', dict): default_values = defaults(base + ['authentication', 'radius', 'server']) - for server in vyos_dict_search('authentication.radius.server', dict): + for server in dict_search('authentication.radius.server', dict): dict['authentication']['radius']['server'][server] = dict_merge( default_values, dict['authentication']['radius']['server'][server]) + # Check option "disable-accounting" per server and replace default value from '1813' to '0' + # set vpn sstp authentication radius server x.x.x.x disable-accounting + if 'disable_accounting' in dict['authentication']['radius']['server'][server]: + dict['authentication']['radius']['server'][server]['acct_port'] = '0' + # Add individual local-user default values - if vyos_dict_search('authentication.local_users.username', dict): + if dict_search('authentication.local_users.username', dict): default_values = defaults(base + ['authentication', 'local-users', 'username']) - for username in vyos_dict_search('authentication.local_users.username', dict): + for username in dict_search('authentication.local_users.username', dict): dict['authentication']['local_users']['username'][username] = dict_merge( default_values, dict['authentication']['local_users']['username'][username]) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 422483663..2a5dc7af2 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -22,7 +22,7 @@ # makes use of it! from vyos import ConfigError -from vyos.util import vyos_dict_search +from vyos.util import dict_search def verify_mtu(config): """ @@ -51,7 +51,7 @@ def verify_mtu_ipv6(config): recurring validation if the specified MTU can be used when IPv6 is configured on the interface. IPv6 requires a 1280 bytes MTU. """ - from vyos.validate import is_ipv6 + from vyos.template import is_ipv6 if 'mtu' in config: # IPv6 minimum required link mtu min_mtu = 1280 @@ -60,19 +60,19 @@ def verify_mtu_ipv6(config): error_msg = f'IPv6 address will be configured on interface "{interface}" ' \ f'thus the minimum MTU requirement is {min_mtu}!' - if not vyos_dict_search('ipv6.address.no_default_link_local', config): - raise ConfigError('link-local ' + error_msg) - - for address in (vyos_dict_search('address', config) or []): + for address in (dict_search('address', config) or []): if address in ['dhcpv6'] or is_ipv6(address): raise ConfigError(error_msg) - if vyos_dict_search('ipv6.address.autoconf', config): - raise ConfigError(error_msg) + tmp = dict_search('ipv6.address', config) + if tmp and 'no_default_link_local' not in tmp: + raise ConfigError('link-local ' + error_msg) - if vyos_dict_search('ipv6.address.eui64', config): + if tmp and 'autoconf' in tmp: raise ConfigError(error_msg) + if tmp and 'eui64' in tmp: + raise ConfigError(error_msg) def verify_vrf(config): """ @@ -154,7 +154,7 @@ def verify_dhcpv6(config): recurring validation of DHCPv6 options which are mutually exclusive. """ if 'dhcpv6_options' in config: - from vyos.util import vyos_dict_search + from vyos.util import dict_search if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']): raise ConfigError('DHCPv6 temporary and parameters-only options ' @@ -162,15 +162,15 @@ def verify_dhcpv6(config): # It is not allowed to have duplicate SLA-IDs as those identify an # assigned IPv6 subnet from a delegated prefix - for pd in vyos_dict_search('dhcpv6_options.pd', config): + for pd in dict_search('dhcpv6_options.pd', config): sla_ids = [] - if not vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config): + if not dict_search(f'dhcpv6_options.pd.{pd}.interface', config): raise ConfigError('DHCPv6-PD requires an interface where to assign ' 'the delegated prefix!') - for interface in vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config): - sla_id = vyos_dict_search( + for interface in dict_search(f'dhcpv6_options.pd.{pd}.interface', config): + sla_id = dict_search( f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id', config) sla_ids.append(sla_id) @@ -211,11 +211,11 @@ def verify_accel_ppp_base_service(config): on get_config_dict() """ # vertify auth settings - if vyos_dict_search('authentication.mode', config) == 'local': - if not vyos_dict_search('authentication.local_users', config): + if dict_search('authentication.mode', config) == 'local': + if not dict_search('authentication.local_users', config): raise ConfigError('PPPoE local auth mode requires local users to be configured!') - for user in vyos_dict_search('authentication.local_users.username', config): + for user in dict_search('authentication.local_users.username', config): user_config = config['authentication']['local_users']['username'][user] if 'password' not in user_config: @@ -227,11 +227,11 @@ def verify_accel_ppp_base_service(config): raise ConfigError(f'User "{user}" has rate-limit configured for only one ' \ 'direction but both upload and download must be given!') - elif vyos_dict_search('authentication.mode', config) == 'radius': - if not vyos_dict_search('authentication.radius.server', config): + elif dict_search('authentication.mode', config) == 'radius': + if not dict_search('authentication.radius.server', config): raise ConfigError('RADIUS authentication requires at least one server') - for server in vyos_dict_search('authentication.radius.server', config): + for server in dict_search('authentication.radius.server', config): radius_config = config['authentication']['radius']['server'][server] if 'key' not in radius_config: raise ConfigError(f'Missing RADIUS secret key for server "{server}"') @@ -259,3 +259,27 @@ def verify_accel_ppp_base_service(config): if 'delegation_prefix' not in ipv6_pool['delegate'][delegate]: raise ConfigError('delegation-prefix length required!') +def verify_diffie_hellman_length(file, min_keysize): + """ Verify Diffie-Hellamn keypair length given via file. It must be greater + then or equal to min_keysize """ + + try: + keysize = str(min_keysize) + except: + return False + + import os + import re + from vyos.util import cmd + + if os.path.exists(file): + + out = cmd(f'openssl dhparam -inform PEM -in {file} -text') + prog = re.compile('\d+\s+bit') + if prog.search(out): + bits = prog.search(out)[0].split()[0] + if int(min_keysize) >= int(bits): + return True + + return False + diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 9108fc180..709222b09 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -17,7 +17,7 @@ import os from vyos.ifconfig.interface import Interface from vyos.util import cmd -from vyos.util import vyos_dict_search +from vyos.util import dict_search from vyos.validate import assert_list from vyos.validate import assert_positive @@ -360,7 +360,7 @@ class BondIf(Interface): self.set_arp_ip_target('-' + addr) # Add configured ARP target addresses - value = vyos_dict_search('arp_monitor.target', config) + value = dict_search('arp_monitor.target', config) if isinstance(value, str): value = [value] if value: @@ -384,7 +384,7 @@ class BondIf(Interface): # Removing an interface from a bond will always place the underlaying # physical interface in admin-down state! If physical interface is # not disabled, re-enable it. - if not vyos_dict_search(f'member.interface_remove.{interface}.disable', config): + if not dict_search(f'member.interface_remove.{interface}.disable', config): Interface(interface).set_admin_state('up') # Bonding policy/mode @@ -392,7 +392,7 @@ class BondIf(Interface): if value: self.set_mode(value) # Add (enslave) interfaces to bond - value = vyos_dict_search('member.interface', config) + value = dict_search('member.interface', config) for interface in (value or []): # if we've come here we already verified the interface # does not have an addresses configured so just flush diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index bf78f8972..7eac9b886 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -19,7 +19,7 @@ from vyos.ifconfig.interface import Interface from vyos.validate import assert_boolean from vyos.validate import assert_positive from vyos.util import cmd -from vyos.util import vyos_dict_search +from vyos.util import dict_search @Interface.register class BridgeIf(Interface): @@ -41,6 +41,7 @@ class BridgeIf(Interface): 'section': 'bridge', 'prefixes': ['br', ], 'broadcast': True, + 'vlan': True, }, } @@ -73,6 +74,10 @@ class BridgeIf(Interface): 'validate': assert_boolean, 'location': '/sys/class/net/{ifname}/bridge/stp_state', }, + 'vlan_filter': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/bridge/vlan_filtering', + }, 'multicast_querier': { 'validate': assert_boolean, 'location': '/sys/class/net/{ifname}/bridge/multicast_querier', @@ -152,6 +157,16 @@ class BridgeIf(Interface): >>> BridgeIf('br0').set_stp(1) """ self.set_interface('stp', state) + + def set_vlan_filter(self, state): + """ + Set bridge Vlan Filter state. 0 -> Vlan Filter disabled, 1 -> Vlan Filter enabled + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_vlan_filter(1) + """ + self.set_interface('vlan_filter', state) def set_multicast_querier(self, enable): """ @@ -177,8 +192,13 @@ class BridgeIf(Interface): >>> BridgeIf('br0').add_port('eth0') >>> BridgeIf('br0').add_port('eth1') """ + # Bridge port handling of wireless interfaces is done by hostapd. + if 'wlan' in interface: + return + return self.set_interface('add_port', interface) + def del_port(self, interface): """ Remove member port from bridge instance. @@ -197,6 +217,8 @@ class BridgeIf(Interface): # call base class first super().update(config) + + ifname = config['ifname'] # Set ageing time value = config.get('aging') @@ -223,17 +245,18 @@ class BridgeIf(Interface): self.set_stp(value) # enable or disable IGMP querier - tmp = vyos_dict_search('igmp.querier', config) + tmp = dict_search('igmp.querier', config) value = '1' if (tmp != None) else '0' self.set_multicast_querier(value) # remove interface from bridge - tmp = vyos_dict_search('member.interface_remove', config) + tmp = dict_search('member.interface_remove', config) for member in (tmp or []): if member in interfaces(): self.del_port(member) + vlan_filter = 0 - tmp = vyos_dict_search('member.interface', config) + tmp = dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): # if interface does yet not exist bail out early and @@ -260,7 +283,51 @@ class BridgeIf(Interface): if 'priority' in interface_config: value = interface_config.get('priority') lower.set_path_priority(value) - + + tmp = dict_search('native_vlan_removed', interface_config) + + if tmp and 'native_vlan_removed' not in interface_config: + vlan_id = tmp + cmd = f'bridge vlan add dev {interface} vid 1 pvid untagged master' + self._cmd(cmd) + cmd = f'bridge vlan del dev {interface} vid {vlan_id}' + self._cmd(cmd) + + tmp = dict_search('allowed_vlan_removed', interface_config) + + + for vlan_id in (tmp or []): + cmd = f'bridge vlan del dev {interface} vid {vlan_id}' + self._cmd(cmd) + + if 'native_vlan' in interface_config: + vlan_filter = 1 + cmd = f'bridge vlan del dev {interface} vid 1' + self._cmd(cmd) + vlan_id = interface_config['native_vlan'] + cmd = f'bridge vlan add dev {interface} vid {vlan_id} pvid untagged master' + self._cmd(cmd) + else: + cmd = f'bridge vlan del dev {interface} vid 1' + self._cmd(cmd) + + if 'allowed_vlan' in interface_config: + vlan_filter = 1 + for vlan in interface_config['allowed_vlan']: + cmd = f'bridge vlan add dev {interface} vid {vlan} master' + self._cmd(cmd) + + + vif = dict_search('vif', config) + if vif: + for vlan_id,vif_config in vif.items(): + cmd = f'bridge vlan add dev {ifname} vid {vlan_id} self master' + self._cmd(cmd) + + # enable/disable Vlan Filter + self.set_vlan_filter(vlan_filter) + + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 1d48941f9..12d1ec265 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -19,7 +19,7 @@ import re from vyos.ifconfig.interface import Interface from vyos.validate import assert_list from vyos.util import run -from vyos.util import vyos_dict_search +from vyos.util import dict_search @Interface.register class EthernetIf(Interface): @@ -282,27 +282,27 @@ class EthernetIf(Interface): self.set_flow_control(value) # GRO (generic receive offload) - tmp = vyos_dict_search('offload_options.generic_receive', config) + tmp = dict_search('offload_options.generic_receive', config) value = tmp if (tmp != None) else 'off' self.set_gro(value) # GSO (generic segmentation offload) - tmp = vyos_dict_search('offload_options.generic_segmentation', config) + tmp = dict_search('offload_options.generic_segmentation', config) value = tmp if (tmp != None) else 'off' self.set_gso(value) # scatter-gather option - tmp = vyos_dict_search('offload_options.scatter_gather', config) + tmp = dict_search('offload_options.scatter_gather', config) value = tmp if (tmp != None) else 'off' self.set_sg(value) # TSO (TCP segmentation offloading) - tmp = vyos_dict_search('offload_options.udp_fragmentation', config) + tmp = dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_tso(value) # UDP fragmentation offloading - tmp = vyos_dict_search('offload_options.udp_fragmentation', config) + tmp = dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_ufo(value) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index ae747e87c..893623284 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -34,9 +34,9 @@ from vyos.configdict import list_diff from vyos.configdict import dict_merge from vyos.template import render from vyos.util import mac2eui64 -from vyos.util import vyos_dict_search -from vyos.validate import is_ipv4 -from vyos.validate import is_ipv6 +from vyos.util import dict_search +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned from vyos.validate import assert_boolean from vyos.validate import assert_list @@ -880,7 +880,7 @@ class Interface(Control): lease_file = f'{config_base}_{ifname}.leases' if enable and 'disable' not in self._config: - if vyos_dict_search('dhcp_options.host_name', self._config) == None: + if dict_search('dhcp_options.host_name', self._config) == None: # read configured system hostname. # maybe change to vyos hostd client ??? hostname = 'vyos' @@ -959,7 +959,7 @@ class Interface(Control): # always ensure DHCPv6 client is stopped (when not configured as client # for IPv6 address or prefix delegation - dhcpv6pd = vyos_dict_search('dhcpv6_options.pd', config) + dhcpv6pd = dict_search('dhcpv6_options.pd', config) if 'dhcpv6' not in new_addr or dhcpv6pd == None: self.del_addr('dhcpv6') @@ -987,64 +987,64 @@ class Interface(Control): self.set_vrf(config.get('vrf', '')) # Configure ARP cache timeout in milliseconds - has default value - tmp = vyos_dict_search('ip.arp_cache_timeout', config) + tmp = dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' self.set_arp_cache_tmo(value) # Configure ARP filter configuration - tmp = vyos_dict_search('ip.disable_arp_filter', config) + tmp = dict_search('ip.disable_arp_filter', config) value = '0' if (tmp != None) else '1' self.set_arp_filter(value) # Configure ARP accept - tmp = vyos_dict_search('ip.enable_arp_accept', config) + tmp = dict_search('ip.enable_arp_accept', config) value = '1' if (tmp != None) else '0' self.set_arp_accept(value) # Configure ARP announce - tmp = vyos_dict_search('ip.enable_arp_announce', config) + tmp = dict_search('ip.enable_arp_announce', config) value = '1' if (tmp != None) else '0' self.set_arp_announce(value) # Configure ARP ignore - tmp = vyos_dict_search('ip.enable_arp_ignore', config) + tmp = dict_search('ip.enable_arp_ignore', config) value = '1' if (tmp != None) else '0' self.set_arp_ignore(value) # Enable proxy-arp on this interface - tmp = vyos_dict_search('ip.enable_proxy_arp', config) + tmp = dict_search('ip.enable_proxy_arp', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp(value) # Enable private VLAN proxy ARP on this interface - tmp = vyos_dict_search('ip.proxy_arp_pvlan', config) + tmp = dict_search('ip.proxy_arp_pvlan', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp_pvlan(value) # IPv4 forwarding - tmp = vyos_dict_search('ip.disable_forwarding', config) + tmp = dict_search('ip.disable_forwarding', config) value = '0' if (tmp != None) else '1' self.set_ipv4_forwarding(value) # IPv6 forwarding - tmp = vyos_dict_search('ipv6.disable_forwarding', config) + tmp = dict_search('ipv6.disable_forwarding', config) value = '0' if (tmp != None) else '1' self.set_ipv6_forwarding(value) # IPv6 router advertisements - tmp = vyos_dict_search('ipv6.address.autoconf', config) + tmp = dict_search('ipv6.address.autoconf', config) value = '2' if (tmp != None) else '1' if 'dhcpv6' in new_addr: value = '2' self.set_ipv6_accept_ra(value) # IPv6 address autoconfiguration - tmp = vyos_dict_search('ipv6.address.autoconf', config) + tmp = dict_search('ipv6.address.autoconf', config) value = '1' if (tmp != None) else '0' self.set_ipv6_autoconf(value) # IPv6 Duplicate Address Detection (DAD) tries - tmp = vyos_dict_search('ipv6.dup_addr_detect_transmits', config) + tmp = dict_search('ipv6.dup_addr_detect_transmits', config) value = tmp if (tmp != None) else '1' self.set_ipv6_dad_messages(value) @@ -1053,7 +1053,7 @@ class Interface(Control): self.set_mtu(config.get('mtu')) # Delete old IPv6 EUI64 addresses before changing MAC - tmp = vyos_dict_search('ipv6.address.eui64_old', config) + tmp = dict_search('ipv6.address.eui64_old', config) if tmp: for addr in tmp: self.del_ipv6_eui64_address(addr) @@ -1068,7 +1068,7 @@ class Interface(Control): self.set_mac(mac) # Manage IPv6 link-local addresses - tmp = vyos_dict_search('ipv6.address.no_default_link_local', config) + tmp = dict_search('ipv6.address.no_default_link_local', config) # we must check explicitly for None type as if the key is set we will # get an empty dict (<class 'dict'>) if tmp is not None: @@ -1077,7 +1077,7 @@ class Interface(Control): self.add_ipv6_eui64_address('fe80::/64') # Add IPv6 EUI-based addresses - tmp = vyos_dict_search('ipv6.address.eui64', config) + tmp = dict_search('ipv6.address.eui64', config) if tmp: for addr in tmp: self.add_ipv6_eui64_address(addr) diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py index 5fd90f9cf..8ed3d5afb 100644 --- a/python/vyos/ifconfig/l2tpv3.py +++ b/python/vyos/ifconfig/l2tpv3.py @@ -68,8 +68,9 @@ class L2TPv3If(Interface): cmd += ' peer_session_id {peer_session_id}' self._cmd(cmd.format(**self.config)) - # interface is always A/D down. It needs to be enabled explicitly - self.set_admin_state('down') + # No need for interface shut down. There exist no function to permanently enable tunnel. + # But you can disable interface permanently with shutdown/disable command. + self.set_admin_state('up') def remove(self): """ @@ -93,4 +94,24 @@ class L2TPv3If(Interface): if self.config['tunnel_id']: cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}' self._cmd(cmd.format(**self.config)) + + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 964ffe383..4122d1a2f 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -179,7 +179,7 @@ class GRETapIf(_Tunnel): default = {'type': 'gretap'} required = ['local', ] - options = ['local', 'remote', ] + options = ['local', 'remote', 'ttl',] updates = ['mtu', ] create = 'ip link add {ifname} type {type}' diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py index b25e32d63..99a592b3e 100644 --- a/python/vyos/ifconfig/vtun.py +++ b/python/vyos/ifconfig/vtun.py @@ -19,6 +19,7 @@ from vyos.ifconfig.interface import Interface class VTunIf(Interface): default = { 'type': 'vtun', + 'device_type': 'tun', } definition = { **Interface.definition, @@ -28,15 +29,44 @@ class VTunIf(Interface): 'bridgeable': True, }, } - - # stub this interface is created in the configure script + options = Interface.options + ['device_type'] def _create(self): - # we can not create this interface as it is managed outside - # it requires configuring OpenVPN + """ Depending on OpenVPN operation mode the interface is created + immediately (e.g. Server mode) or once the connection to the server is + established (client mode). The latter will only be brought up once the + server can be reached, thus we might need to create this interface in + advance for the service to be operational. """ + try: + cmd = 'openvpn --mktun --dev-type {device_type} --dev {ifname}'.format(**self.config) + return self._cmd(cmd) + except PermissionError: + # interface created by OpenVPN daemon in the meantime ... + pass + + def add_addr(self, addr): + # IP addresses are managed by OpenVPN daemon pass - def _delete(self): - # we can not create this interface as it is managed outside - # it requires configuring OpenVPN + def del_addr(self, addr): + # IP addresses are managed by OpenVPN daemon pass + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index d8e89229d..da3bd4e89 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -24,7 +24,7 @@ from hurry.filesize import alternative from vyos.config import Config from vyos.ifconfig import Interface from vyos.ifconfig import Operational -from vyos.validate import is_ipv6 +from vyos.template import is_ipv6 class WireGuardOperational(Operational): def _dump(self): diff --git a/python/vyos/template.py b/python/vyos/template.py index c88ab04a0..58ba75972 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -16,7 +16,6 @@ import functools import os -from ipaddress import ip_network from jinja2 import Environment from jinja2 import FileSystemLoader from vyos.defaults import directories @@ -124,20 +123,95 @@ def render( # Custom template filters follow # ################################## - -@register_filter("address_from_cidr") -def vyos_address_from_cidr(text): +@register_filter('address_from_cidr') +def address_from_cidr(text): """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address". Example: 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8:: """ - return ip_network(text).network_address - + from ipaddress import ip_network + return str(ip_network(text).network_address) -@register_filter("netmask_from_cidr") -def vyos_netmask_from_cidr(text): - """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask". +@register_filter('netmask_from_cidr') +def netmask_from_cidr(text): + """ Take CIDR prefix and convert the prefix length to a "subnet mask". + Example: + - 192.0.2.0/24 -> 255.255.255.0 + - 2001:db8::/48 -> ffff:ffff:ffff:: + """ + from ipaddress import ip_network + return str(ip_network(text).netmask) + +@register_filter('is_ip') +def is_ip(addr): + """ Check addr if it is an IPv4 or IPv6 address """ + return is_ipv4(addr) or is_ipv6(addr) + +@register_filter('is_ipv4') +def is_ipv4(text): + """ Filter IP address, return True on IPv4 address, False otherwise """ + from ipaddress import ip_interface + try: return ip_interface(text).version == 4 + except: return False + +@register_filter('ipv6') +def is_ipv6(text): + """ Filter IP address, return True on IPv6 address, False otherwise """ + from ipaddress import ip_interface + try: return ip_interface(text).version == 6 + except: return False + +@register_filter('first_host_address') +def first_host_address(text): + """ Return first usable (host) IP address from given prefix. Example: - 192.0.2.0/24 -> 255.255.255.0, 2001:db8::/48 -> ffff:ffff:ffff:: + - 10.0.0.0/24 -> 10.0.0.1 + - 2001:db8::/64 -> 2001:db8:: + """ + from ipaddress import ip_interface + from ipaddress import IPv4Network + from ipaddress import IPv6Network + + addr = ip_interface(text) + if addr.version == 4: + return str(addr.ip +1) + return str(addr.ip) + +@register_filter('last_host_address') +def last_host_address(text): + """ Return first usable IP address from given prefix. + Example: + - 10.0.0.0/24 -> 10.0.0.254 + - 2001:db8::/64 -> 2001:db8::ffff:ffff:ffff:ffff + """ + from ipaddress import ip_interface + from ipaddress import IPv4Network + from ipaddress import IPv6Network + + addr = ip_interface(text) + if addr.version == 4: + return str(IPv4Network(addr).broadcast_address - 1) + + return str(IPv6Network(addr).broadcast_address) + +@register_filter('inc_ip') +def inc_ip(address, increment): + """ Increment given IP address by 'increment' + + Example (inc by 2): + - 10.0.0.0/24 -> 10.0.0.2 + - 2001:db8::/64 -> 2001:db8::2 + """ + from ipaddress import ip_interface + return str(ip_interface(address).ip + int(increment)) + +@register_filter('dec_ip') +def dec_ip(address, decrement): + """ Decrement given IP address by 'decrement' + + Example (inc by 2): + - 10.0.0.0/24 -> 10.0.0.2 + - 2001:db8::/64 -> 2001:db8::2 """ - return ip_network(text).netmask + from ipaddress import ip_interface + return str(ip_interface(address).ip - int(decrement)) diff --git a/python/vyos/util.py b/python/vyos/util.py index b5f0ea36e..fc6915687 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -581,77 +581,6 @@ def get_half_cpus(): cpu /= 2 return int(cpu) -def ifname_from_config(conf): - """ - Gets interface name with VLANs from current config level. - Level must be at the interface whose name we want. - - Example: - >>> from vyos.util import ifname_from_config - >>> from vyos.config import Config - >>> conf = Config() - >>> conf.set_level('interfaces ethernet eth0 vif-s 1 vif-c 2') - >>> ifname_from_config(conf) - 'eth0.1.2' - """ - level = conf.get_level() - - # vlans - if level[-2] == 'vif' or level[-2] == 'vif-s': - return level[-3] + '.' + level[-1] - if level[-2] == 'vif-c': - return level[-5] + '.' + level[-3] + '.' + level[-1] - - # no vlans - return level[-1] - -def get_bridge_member_config(conf, br, intf): - """ - Gets bridge port (member) configuration - - Arguments: - conf: Config - br: bridge name - intf: interface name - - Returns: - dict with the configuration - False if bridge or bridge port doesn't exist - """ - old_level = conf.get_level() - conf.set_level([]) - - bridge = f'interfaces bridge {br}' - member = f'{bridge} member interface {intf}' - if not ( conf.exists(bridge) and conf.exists(member) ): - return False - - # default bridge port configuration - # cost and priority initialized with linux defaults - # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} - # after adding interface to bridge after reboot - memberconf = { - 'cost': 100, - 'priority': 32, - 'arp_cache_tmo': 30, - 'disable_link_detect': 1, - } - - if conf.exists(f'{member} cost'): - memberconf['cost'] = int(conf.return_value(f'{member} cost')) - - if conf.exists(f'{member} priority'): - memberconf['priority'] = int(conf.return_value(f'{member} priority')) - - if conf.exists(f'{bridge} ip arp-cache-timeout'): - memberconf['arp_cache_tmo'] = int(conf.return_value(f'{bridge} ip arp-cache-timeout')) - - if conf.exists(f'{bridge} disable-link-detect'): - memberconf['disable_link_detect'] = 2 - - conf.set_level(old_level) - return memberconf - def check_kmod(k_mod): """ Common utility function to load required kernel modules on demand """ from vyos import ConfigError @@ -674,7 +603,7 @@ def find_device_file(device): return None -def vyos_dict_search(path, dict): +def dict_search(path, dict): """ Traverse Python dictionary (dict) delimited by dot (.). Return value of key if found, None otherwise. diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 691cf3c8e..84a7bc2de 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -1,4 +1,4 @@ -# Copyright 2018 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2018-2020 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -13,11 +13,7 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. -import json -import socket import netifaces -import ipaddress - from vyos.util import cmd # Important note when you are adding new validation functions: @@ -29,60 +25,26 @@ from vyos.util import cmd # parameters with default will be left unset # all other paramters will receive the value to check - -def is_ip(addr): - """ - Check addr if it is an IPv4 or IPv6 address - """ - return is_ipv4(addr) or is_ipv6(addr) - -def is_ipv4(addr): - """ - Check addr if it is an IPv4 address/network. Returns True/False - """ - - # With the below statement we can check for IPv4 networks and host - # addresses at the same time - try: - if ipaddress.ip_address(addr.split(r'/')[0]).version == 4: - return True - except: - pass - - return False - -def is_ipv6(addr): - """ - Check addr if it is an IPv6 address/network. Returns True/False - """ - - # With the below statement we can check for IPv4 networks and host - # addresses at the same time - try: - if ipaddress.ip_network(addr.split(r'/')[0]).version == 6: - return True - except: - pass - - return False - def is_ipv6_link_local(addr): - """ - Check addr if it is an IPv6 link-local address/network. Returns True/False - """ - + """ Check if addrsss is an IPv6 link-local address. Returns True/False """ + from ipaddress import IPv6Address + from vyos.template import is_ipv6 addr = addr.split('%')[0] if is_ipv6(addr): - if ipaddress.IPv6Address(addr).is_link_local: + if IPv6Address(addr).is_link_local: return True return False def _are_same_ip(one, two): + from socket import AF_INET + from socket import AF_INET6 + from socket import inet_pton + from vyos.template import is_ipv4 # compare the binary representation of the IP - f_one = socket.AF_INET if is_ipv4(one) else socket.AF_INET6 - s_two = socket.AF_INET if is_ipv4(two) else socket.AF_INET6 - return socket.inet_pton(f_one, one) == socket.inet_pton(f_one, two) + f_one = AF_INET if is_ipv4(one) else AF_INET6 + s_two = AF_INET if is_ipv4(two) else AF_INET6 + return inet_pton(f_one, one) == inet_pton(f_one, two) def is_intf_addr_assigned(intf, addr): if '/' in addr: @@ -96,6 +58,7 @@ def _is_intf_addr_assigned(intf, address, netmask=''): It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. """ + from vyos.template import is_ipv4 # check if the requested address type is configured at all # { @@ -149,10 +112,9 @@ def is_addr_assigned(addr): return False def is_loopback_addr(addr): - """ - Check if supplied IPv4/IPv6 address is a loopback address - """ - return ipaddress.ip_address(addr).is_loopback + """ Check if supplied IPv4/IPv6 address is a loopback address """ + from ipaddress import ip_address + return ip_address(addr).is_loopback def is_subnet_connected(subnet, primary=False): """ @@ -165,6 +127,9 @@ def is_subnet_connected(subnet, primary=False): Return True/False """ + from ipaddress import ip_address + from ipaddress import ip_network + from vyos.template import is_ipv6 # determine IP version (AF_INET or AF_INET6) depending on passed address addr_type = netifaces.AF_INET @@ -180,7 +145,7 @@ def is_subnet_connected(subnet, primary=False): # only support the primary address :( if primary: ip = netifaces.ifaddresses(interface)[addr_type][0]['addr'] - if ipaddress.ip_address(ip) in ipaddress.ip_network(subnet): + if ip_address(ip) in ip_network(subnet): return True else: # Check every assigned IP address if it is connected to the subnet @@ -188,7 +153,7 @@ def is_subnet_connected(subnet, primary=False): for ip in netifaces.ifaddresses(interface)[addr_type]: # remove interface extension (e.g. %eth0) that gets thrown on the end of _some_ addrs addr = ip['addr'].split('%')[0] - if ipaddress.ip_address(addr) in ipaddress.ip_network(subnet): + if ip_address(addr) in ip_network(subnet): return True return False @@ -224,6 +189,7 @@ def assert_positive(n, smaller=0): def assert_mtu(mtu, ifname): assert_number(mtu) + import json out = cmd(f'ip -j -d link show dev {ifname}') # [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"pfifo_fast","operstate":"UP","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"ether","address":"08:00:27:d9:5b:04","broadcast":"ff:ff:ff:ff:ff:ff","promiscuity":0,"min_mtu":46,"max_mtu":16110,"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535}] parsed = json.loads(out)[0] @@ -265,7 +231,6 @@ def assert_mac(m): if octets[:5] == (0, 0, 94, 0, 1): raise ValueError(f'{m} is a VRRP MAC address') - def has_address_configured(conf, intf): """ Checks if interface has an address configured. |