diff options
Diffstat (limited to 'python/vyos/ifconfig/interface.py')
-rw-r--r-- | python/vyos/ifconfig/interface.py | 384 |
1 files changed, 248 insertions, 136 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index c50ead89f..050095364 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1,4 +1,4 @@ -# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2023 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 @@ -31,23 +31,26 @@ from vyos import ConfigError from vyos.configdict import list_diff from vyos.configdict import dict_merge from vyos.configdict import get_vlan_ids +from vyos.defaults import directories from vyos.template import render -from vyos.util import mac2eui64 -from vyos.util import dict_search -from vyos.util import read_file -from vyos.util import get_interface_config -from vyos.util import get_interface_namespace -from vyos.util import is_systemd_service_active +from vyos.utils.network import mac2eui64 +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_interface_namespace +from vyos.utils.network import is_netns_interface +from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import run from vyos.template import is_ipv4 from vyos.template import is_ipv6 -from vyos.validate import is_intf_addr_assigned -from vyos.validate import is_ipv6_link_local -from vyos.validate import assert_boolean -from vyos.validate import assert_list -from vyos.validate import assert_mac -from vyos.validate import assert_mtu -from vyos.validate import assert_positive -from vyos.validate import assert_range +from vyos.utils.network import is_intf_addr_assigned +from vyos.utils.network import is_ipv6_link_local +from vyos.utils.assertion import assert_boolean +from vyos.utils.assertion import assert_list +from vyos.utils.assertion import assert_mac +from vyos.utils.assertion import assert_mtu +from vyos.utils.assertion import assert_positive +from vyos.utils.assertion import assert_range from vyos.ifconfig.control import Control from vyos.ifconfig.vrrp import VRRP @@ -57,6 +60,8 @@ from vyos.ifconfig import Section from netaddr import EUI from netaddr import mac_unix_expanded +link_local_prefix = 'fe80::/64' + class Interface(Control): # This is the class which will be used to create # self.operational, it allows subclasses, such as @@ -135,9 +140,6 @@ class Interface(Control): 'validate': assert_mtu, 'shellcmd': 'ip link set dev {ifname} mtu {value}', }, - 'netns': { - 'shellcmd': 'ip link set dev {ifname} netns {value}', - }, 'vrf': { 'convert': lambda v: f'master {v}' if v else 'nomaster', 'shellcmd': 'ip link set dev {ifname} {value}', @@ -172,10 +174,6 @@ class Interface(Control): 'validate': assert_boolean, 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding', }, - 'rp_filter': { - 'validate': lambda flt: assert_range(flt,0,3), - 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter', - }, 'ipv6_accept_ra': { 'validate': lambda ara: assert_range(ara,0,3), 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', @@ -188,6 +186,10 @@ class Interface(Control): 'validate': lambda fwd: assert_range(fwd,0,2), 'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding', }, + 'ipv6_accept_dad': { + 'validate': lambda dad: assert_range(dad,0,3), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_dad', + }, 'ipv6_dad_transmits': { 'validate': assert_positive, 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits', @@ -217,6 +219,10 @@ class Interface(Control): 'validate': lambda link: assert_range(link,0,3), 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter', }, + 'per_client_thread': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/threaded', + }, } _sysfs_get = { @@ -241,9 +247,6 @@ class Interface(Control): 'ipv4_directed_broadcast': { 'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding', }, - 'rp_filter': { - 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter', - }, 'ipv6_accept_ra': { 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', }, @@ -253,6 +256,9 @@ class Interface(Control): 'ipv6_forwarding': { 'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding', }, + 'ipv6_accept_dad': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_dad', + }, 'ipv6_dad_transmits': { 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits', }, @@ -265,11 +271,18 @@ class Interface(Control): 'link_detect': { 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter', }, + 'per_client_thread': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/threaded', + }, } @classmethod - def exists(cls, ifname): - return os.path.exists(f'/sys/class/net/{ifname}') + def exists(cls, ifname: str, netns: str=None) -> bool: + cmd = f'ip link show dev {ifname}' + if netns: + cmd = f'ip netns exec {netns} {cmd}' + return run(cmd) == 0 @classmethod def get_config(cls): @@ -337,7 +350,13 @@ class Interface(Control): self.vrrp = VRRP(ifname) def _create(self): + # Do not create interface that already exist or exists in netns + netns = self.config.get('netns', None) + if self.exists(f'{self.ifname}', netns=netns): + return + cmd = 'ip link add dev {ifname} type {type}'.format(**self.config) + if 'netns' in self.config: cmd = f'ip netns exec {netns} {cmd}' self._cmd(cmd) def remove(self): @@ -372,6 +391,9 @@ class Interface(Control): # after interface removal no other commands should be allowed # to be called and instead should raise an Exception: cmd = 'ip link del dev {ifname}'.format(**self.config) + # for delete we can't get data from self.config{'netns'} + netns = get_interface_namespace(self.ifname) + if netns: cmd = f'ip netns exec {netns} {cmd}' return self._cmd(cmd) def _set_vrf_ct_zone(self, vrf): @@ -379,6 +401,10 @@ class Interface(Control): Add/Remove rules in nftables to associate traffic in VRF to an individual conntack zone """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + if vrf: # Get routing table ID for VRF vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get( @@ -522,36 +548,30 @@ class Interface(Control): if prev_state == 'up': self.set_admin_state('up') - def del_netns(self, netns): - """ - Remove interface from given NETNS. - """ - - # If NETNS does not exist then there is nothing to delete + def del_netns(self, netns: str) -> bool: + """ Remove interface from given network namespace """ + # If network namespace does not exist then there is nothing to delete if not os.path.exists(f'/run/netns/{netns}'): - return None - - # As a PoC we only allow 'dummy' interfaces - if 'dum' not in self.ifname: - return None + return False - # Check if interface realy exists in namespace - if get_interface_namespace(self.ifname) != None: - self._cmd(f'ip netns exec {get_interface_namespace(self.ifname)} ip link del dev {self.ifname}') - return + # Check if interface exists in network namespace + if is_netns_interface(self.ifname, netns): + self._cmd(f'ip netns exec {netns} ip link del dev {self.ifname}') + return True + return False - def set_netns(self, netns): + def set_netns(self, netns: str) -> bool: """ - Add interface from given NETNS. + Add interface from given network namespace Example: >>> from vyos.ifconfig import Interface >>> Interface('dum0').set_netns('foo') """ + self._cmd(f'ip link set dev {self.ifname} netns {netns}') + return True - self.set_interface('netns', netns) - - def set_vrf(self, vrf): + def set_vrf(self, vrf: str) -> bool: """ Add/Remove interface from given VRF instance. @@ -563,10 +583,11 @@ class Interface(Control): tmp = self.get_interface('vrf') if tmp == vrf: - return None + return False self.set_interface('vrf', vrf) self._set_vrf_ct_zone(vrf) + return True def set_arp_cache_tmo(self, tmo): """ @@ -603,6 +624,10 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_tcp_ipv4_mss(1340) """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + self._cleanup_mss_rules('raw', self.ifname) nft_prefix = 'nft add rule raw VYOS_TCP_MSS' base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn' @@ -623,6 +648,10 @@ class Interface(Control): >>> from vyos.ifconfig import Interface >>> Interface('eth0').set_tcp_mss(1320) """ + # Don't allow for netns yet + if 'netns' in self.config: + return None + self._cleanup_mss_rules('ip6 raw', self.ifname) nft_prefix = 'nft add rule ip6 raw VYOS_TCP_MSS' base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn' @@ -727,37 +756,63 @@ class Interface(Control): return None return self.set_interface('ipv4_directed_broadcast', forwarding) - def set_ipv4_source_validation(self, value): + def _cleanup_ipv4_source_validation_rules(self, ifname): + results = self._cmd(f'nft -a list chain ip raw vyos_rpfilter').split("\n") + for line in results: + if f'iifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule ip raw vyos_rpfilter handle {handle_search[1]}') + + def set_ipv4_source_validation(self, mode): """ - Help prevent attacks used by Spoofing IP Addresses. Reverse path - filtering is a Kernel feature that, when enabled, is designed to ensure - packets that are not routable to be dropped. The easiest example of this - would be and IP Address of the range 10.0.0.0/8, a private IP Address, - being received on the Internet facing interface of the router. + Set IPv4 reverse path validation - As per RFC3074. + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv4_source_validation('strict') """ - if value == 'strict': - value = 1 - elif value == 'loose': - value = 2 - else: - value = 0 + # Don't allow for netns yet + if 'netns' in self.config: + return None - all_rp_filter = int(read_file('/proc/sys/net/ipv4/conf/all/rp_filter')) - if all_rp_filter > value: - global_setting = 'disable' - if all_rp_filter == 1: global_setting = 'strict' - elif all_rp_filter == 2: global_setting = 'loose' + self._cleanup_ipv4_source_validation_rules(self.ifname) + nft_prefix = f'nft insert rule ip raw vyos_rpfilter iifname "{self.ifname}"' + if mode in ['strict', 'loose']: + self._cmd(f"{nft_prefix} counter return") + if mode == 'strict': + self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") + elif mode == 'loose': + self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop") + + def _cleanup_ipv6_source_validation_rules(self, ifname): + results = self._cmd(f'nft -a list chain ip6 raw vyos_rpfilter').split("\n") + for line in results: + if f'iifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule ip6 raw vyos_rpfilter handle {handle_search[1]}') - from vyos.base import Warning - Warning(f'Global source-validation is set to "{global_setting} '\ - f'this overrides per interface setting!') + def set_ipv6_source_validation(self, mode): + """ + Set IPv6 reverse path validation - tmp = self.get_interface('rp_filter') - if int(tmp) == value: + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv6_source_validation('strict') + """ + # Don't allow for netns yet + if 'netns' in self.config: return None - return self.set_interface('rp_filter', value) + + self._cleanup_ipv6_source_validation_rules(self.ifname) + nft_prefix = f'nft insert rule ip6 raw vyos_rpfilter iifname "{self.ifname}"' + if mode in ['strict', 'loose']: + self._cmd(f"{nft_prefix} counter return") + if mode == 'strict': + self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") + elif mode == 'loose': + self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop") def set_ipv6_accept_ra(self, accept_ra): """ @@ -843,6 +898,13 @@ class Interface(Control): return None return self.set_interface('ipv6_forwarding', forwarding) + def set_ipv6_dad_accept(self, dad): + """Whether to accept DAD (Duplicate Address Detection)""" + tmp = self.get_interface('ipv6_accept_dad') + if tmp == dad: + return None + return self.set_interface('ipv6_accept_dad', dad) + def set_ipv6_dad_messages(self, dad): """ The amount of Duplicate Address Detection probes to send. @@ -1094,13 +1156,17 @@ class Interface(Control): if addr in self._addr: return False + # get interface network namespace if specified + netns = self.config.get('netns', None) + # add to interface if addr == 'dhcp': self.set_dhcp(True) elif addr == 'dhcpv6': self.set_dhcpv6(True) - elif not is_intf_addr_assigned(self.ifname, addr): - tmp = f'ip addr add {addr} dev {self.ifname}' + elif not is_intf_addr_assigned(self.ifname, addr, netns=netns): + netns_cmd = f'ip netns exec {netns}' if netns else '' + tmp = f'{netns_cmd} ip addr add {addr} dev {self.ifname}' # Add broadcast address for IPv4 if is_ipv4(addr): tmp += ' brd +' @@ -1140,13 +1206,17 @@ class Interface(Control): if not addr: raise ValueError() + # get interface network namespace if specified + netns = self.config.get('netns', None) + # remove from interface if addr == 'dhcp': self.set_dhcp(False) elif addr == 'dhcpv6': self.set_dhcpv6(False) - elif is_intf_addr_assigned(self.ifname, addr): - self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') + elif is_intf_addr_assigned(self.ifname, addr, netns=netns): + netns_cmd = f'ip netns exec {netns}' if netns else '' + self._cmd(f'{netns_cmd} ip addr del {addr} dev {self.ifname}') else: return False @@ -1166,8 +1236,11 @@ class Interface(Control): self.set_dhcp(False) self.set_dhcpv6(False) + netns = get_interface_namespace(self.ifname) + netns_cmd = f'ip netns exec {netns}' if netns else '' + cmd = f'{netns_cmd} ip addr flush dev {self.ifname}' # flush all addresses - self._cmd(f'ip addr flush dev "{self.ifname}"') + self._cmd(cmd) def add_to_bridge(self, bridge_dict): """ @@ -1238,44 +1311,49 @@ class Interface(Control): raise ValueError() ifname = self.ifname - config_base = r'/var/lib/dhcp/dhclient' - config_file = f'{config_base}_{ifname}.conf' - options_file = f'{config_base}_{ifname}.options' - pid_file = f'{config_base}_{ifname}.pid' - lease_file = f'{config_base}_{ifname}.leases' + config_base = directories['isc_dhclient_dir'] + '/dhclient' + dhclient_config_file = f'{config_base}_{ifname}.conf' + dhclient_lease_file = f'{config_base}_{ifname}.leases' + systemd_override_file = f'/run/systemd/system/dhclient@{ifname}.service.d/10-override.conf' systemd_service = f'dhclient@{ifname}.service' + # Rendered client configuration files require the apsolute config path + self.config['isc_dhclient_dir'] = directories['isc_dhclient_dir'] + # 'up' check is mandatory b/c even if the interface is A/D, as soon as # the DHCP client is started the interface will be placed in u/u state. # This is not what we intended to do when disabling an interface. - if enable and 'disable' not in self._config: - if dict_search('dhcp_options.host_name', self._config) == None: + if enable and 'disable' not in self.config: + if dict_search('dhcp_options.host_name', self.config) == None: # read configured system hostname. # maybe change to vyos hostd client ??? hostname = 'vyos' with open('/etc/hostname', 'r') as f: hostname = f.read().rstrip('\n') tmp = {'dhcp_options' : { 'host_name' : hostname}} - self._config = dict_merge(tmp, self._config) + self.config = dict_merge(tmp, self.config) + + render(systemd_override_file, 'dhcp-client/override.conf.j2', self.config) + render(dhclient_config_file, 'dhcp-client/ipv4.j2', self.config) - render(options_file, 'dhcp-client/daemon-options.j2', self._config) - render(config_file, 'dhcp-client/ipv4.j2', self._config) + # Reload systemd unit definitons as some options are dynamically generated + self._cmd('systemctl daemon-reload') # When the DHCP client is restarted a brief outage will occur, as # the old lease is released a new one is acquired (T4203). We will # only restart DHCP client if it's option changed, or if it's not # running, but it should be running (e.g. on system startup) - if 'dhcp_options_changed' in self._config or not is_systemd_service_active(systemd_service): + if 'dhcp_options_changed' in self.config or not is_systemd_service_active(systemd_service): return self._cmd(f'systemctl restart {systemd_service}') - return None else: if is_systemd_service_active(systemd_service): self._cmd(f'systemctl stop {systemd_service}') # cleanup old config files - for file in [config_file, options_file, pid_file, lease_file]: + for file in [dhclient_config_file, systemd_override_file, dhclient_lease_file]: if os.path.isfile(file): os.remove(file) + return None def set_dhcpv6(self, enable): """ @@ -1285,11 +1363,20 @@ class Interface(Control): raise ValueError() ifname = self.ifname - config_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + config_base = directories['dhcp6_client_dir'] + config_file = f'{config_base}/dhcp6c.{ifname}.conf' + systemd_override_file = f'/run/systemd/system/dhcp6c@{ifname}.service.d/10-override.conf' systemd_service = f'dhcp6c@{ifname}.service' - if enable and 'disable' not in self._config: - render(config_file, 'dhcp-client/ipv6.j2', self._config) + # Rendered client configuration files require the apsolute config path + self.config['dhcp6_client_dir'] = directories['dhcp6_client_dir'] + + if enable and 'disable' not in self.config: + render(systemd_override_file, 'dhcp-client/ipv6.override.conf.j2', self.config) + render(config_file, 'dhcp-client/ipv6.j2', self.config) + + # Reload systemd unit definitons as some options are dynamically generated + self._cmd('systemctl daemon-reload') # We must ignore any return codes. This is required to enable # DHCPv6-PD for interfaces which are yet not up and running. @@ -1300,26 +1387,33 @@ class Interface(Control): if os.path.isfile(config_file): os.remove(config_file) + return None + def set_mirror_redirect(self): # Please refer to the document for details # - https://man7.org/linux/man-pages/man8/tc.8.html # - https://man7.org/linux/man-pages/man8/tc-mirred.8.html # Depening if we are the source or the target interface of the port # mirror we need to setup some variables. - source_if = self._config['ifname'] + + # Don't allow for netns yet + if 'netns' in self.config: + return None + + source_if = self.config['ifname'] mirror_config = None - if 'mirror' in self._config: - mirror_config = self._config['mirror'] - if 'is_mirror_intf' in self._config: - source_if = next(iter(self._config['is_mirror_intf'])) - mirror_config = self._config['is_mirror_intf'][source_if].get('mirror', None) + if 'mirror' in self.config: + mirror_config = self.config['mirror'] + if 'is_mirror_intf' in self.config: + source_if = next(iter(self.config['is_mirror_intf'])) + mirror_config = self.config['is_mirror_intf'][source_if].get('mirror', None) redirect_config = None # clear existing ingess - ignore errors (e.g. "Error: Cannot find specified # qdisc on specified device") - we simply cleanup all stuff here - if not 'traffic_policy' in self._config: + if not 'traffic_policy' in self.config: self._popen(f'tc qdisc del dev {source_if} parent ffff: 2>/dev/null'); self._popen(f'tc qdisc del dev {source_if} parent 1: 2>/dev/null'); @@ -1343,43 +1437,39 @@ class Interface(Control): if err: print('tc qdisc(filter for mirror port failed') # Apply interface traffic redirection policy - elif 'redirect' in self._config: + elif 'redirect' in self.config: _, err = self._popen(f'tc qdisc add dev {source_if} handle ffff: ingress') if err: print(f'tc qdisc add for redirect failed!') - target_if = self._config['redirect'] + target_if = self.config['redirect'] _, err = self._popen(f'tc filter add dev {source_if} parent ffff: protocol '\ f'all prio 10 u32 match u32 0 0 flowid 1:1 action mirred '\ f'egress redirect dev {target_if}') if err: print('tc filter add for redirect failed') - def set_xdp(self, state): + def set_per_client_thread(self, enable): """ - Enable Kernel XDP support. State can be either True or False. + Per-device control to enable/disable the threaded mode for all the napi + instances of the given network device, without the need for a device up/down. + + User sets it to 1 or 0 to enable or disable threaded mode. Example: >>> from vyos.ifconfig import Interface - >>> i = Interface('eth0') - >>> i.set_xdp(True) - """ - if not isinstance(state, bool): - raise ValueError("Value out of range") - - # https://phabricator.vyos.net/T3448 - there is (yet) no RPI support for XDP - if not os.path.exists('/usr/sbin/xdp_loader'): - return - - ifname = self.config['ifname'] - cmd = f'xdp_loader -d {ifname} -U --auto-mode' - if state: - # Using 'xdp' will automatically decide if the driver supports - # 'xdpdrv' or only 'xdpgeneric'. A user later sees which driver is - # actually in use by calling 'ip a' or 'show interfaces ethernet' - cmd = f'xdp_loader -d {ifname} --auto-mode -F --progsec xdp_router ' \ - f'--filename /usr/share/vyos/xdp/xdp_prog_kern.o && ' \ - f'xdp_prog_user -d {ifname}' + >>> Interface('wg1').set_per_client_thread(1) + """ + # In the case of a "virtual" interface like wireguard, the sysfs + # node is only created once there is a peer configured. We can now + # add a verify() code-path for this or make this dynamic without + # nagging the user + tmp = self._sysfs_get['per_client_thread']['location'] + if not os.path.exists(tmp): + return None - return self._cmd(cmd) + tmp = self.get_interface('per_client_thread') + if tmp == enable: + return None + self.set_interface('per_client_thread', enable) def update(self, config): """ General helper function which works on a dictionary retrived by @@ -1394,7 +1484,7 @@ class Interface(Control): # Cache the configuration - it will be reused inside e.g. DHCP handler # XXX: maybe pass the option via __init__ in the future and rename this # method to apply()? - self._config = config + self.config = config # Change interface MAC address - re-set to real hardware address (hw-id) # if custom mac is removed. Skip if bond member. @@ -1410,8 +1500,8 @@ class Interface(Control): # Since the interface is pushed onto a separate logical stack # Configure NETNS if dict_search('netns', config) != None: - self.set_netns(config.get('netns', '')) - return + if not is_netns_interface(self.ifname, self.config['netns']): + self.set_netns(config.get('netns', '')) else: self.del_netns(config.get('netns', '')) @@ -1444,7 +1534,7 @@ class Interface(Control): # we will delete all interface specific IP addresses if they are not # explicitly configured on the CLI if is_ipv6_link_local(addr): - eui64 = mac2eui64(self.get_mac(), 'fe80::/64') + eui64 = mac2eui64(self.get_mac(), link_local_prefix) if addr != f'{eui64}/64': self.del_addr(addr) else: @@ -1531,6 +1621,11 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_ipv4_source_validation(value) + # IPv6 source-validation + tmp = dict_search('ipv6.source_validation', config) + value = tmp if (tmp != None) else '0' + self.set_ipv6_source_validation(value) + # MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set # before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be # automatically disabled by the kernel. Also MTU must be increased before @@ -1560,10 +1655,17 @@ class Interface(Control): value = '1' if (tmp != None) else '0' self.set_ipv6_autoconf(value) - # IPv6 Duplicate Address Detection (DAD) tries + # Whether to accept IPv6 DAD (Duplicate Address Detection) packets + tmp = dict_search('ipv6.accept_dad', config) + # Not all interface types got this CLI option, but if they do, there + # is an XML defaultValue available + if (tmp != None): self.set_ipv6_dad_accept(tmp) + + # IPv6 DAD tries tmp = dict_search('ipv6.dup_addr_detect_transmits', config) - value = tmp if (tmp != None) else '1' - self.set_ipv6_dad_messages(value) + # Not all interface types got this CLI option, but if they do, there + # is an XML defaultValue available + if (tmp != None): self.set_ipv6_dad_messages(tmp) # Delete old IPv6 EUI64 addresses before changing MAC for addr in (dict_search('ipv6.address.eui64_old', config) or []): @@ -1571,9 +1673,9 @@ class Interface(Control): # Manage IPv6 link-local addresses if dict_search('ipv6.address.no_default_link_local', config) != None: - self.del_ipv6_eui64_address('fe80::/64') + self.del_ipv6_eui64_address(link_local_prefix) else: - self.add_ipv6_eui64_address('fe80::/64') + self.add_ipv6_eui64_address(link_local_prefix) # Add IPv6 EUI-based addresses tmp = dict_search('ipv6.address.eui64', config) @@ -1586,12 +1688,14 @@ class Interface(Control): tmp = config.get('is_bridge_member') self.add_to_bridge(tmp) - # eXpress Data Path - highly experimental - self.set_xdp('xdp' in config) - # configure interface mirror or redirection target self.set_mirror_redirect() + # enable/disable NAPI threading mode + tmp = dict_search('per_client_thread', config) + value = '1' if (tmp != None) else '0' + self.set_per_client_thread(value) + # 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 @@ -1709,6 +1813,14 @@ class VLANIf(Interface): if self.exists(f'{self.ifname}'): return + # If source_interface or vlan_id was not explicitly defined (e.g. when + # calling VLANIf('eth0.1').remove() we can define source_interface and + # vlan_id here, as it's quiet obvious that it would be eth0 in that case. + if 'source_interface' not in self.config: + self.config['source_interface'] = '.'.join(self.ifname.split('.')[:-1]) + if 'vlan_id' not in self.config: + self.config['vlan_id'] = self.ifname.split('.')[-1] + cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}' if 'protocol' in self.config: cmd += ' protocol {protocol}' |