diff options
Diffstat (limited to 'python/vyos/ifconfig')
-rw-r--r-- | python/vyos/ifconfig/__init__.py | 4 | ||||
-rw-r--r-- | python/vyos/ifconfig/bond.py | 8 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 8 | ||||
-rw-r--r-- | python/vyos/ifconfig/control.py | 22 | ||||
-rw-r--r-- | python/vyos/ifconfig/ethernet.py | 43 | ||||
-rw-r--r-- | python/vyos/ifconfig/geneve.py | 3 | ||||
-rw-r--r-- | python/vyos/ifconfig/input.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 384 | ||||
-rw-r--r-- | python/vyos/ifconfig/l2tpv3.py | 5 | ||||
-rw-r--r-- | python/vyos/ifconfig/loopback.py | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig/macsec.py | 22 | ||||
-rw-r--r-- | python/vyos/ifconfig/macvlan.py | 9 | ||||
-rw-r--r-- | python/vyos/ifconfig/operational.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/pppoe.py | 4 | ||||
-rw-r--r-- | python/vyos/ifconfig/sstpc.py | 40 | ||||
-rw-r--r-- | python/vyos/ifconfig/tunnel.py | 18 | ||||
-rw-r--r-- | python/vyos/ifconfig/veth.py | 54 | ||||
-rw-r--r-- | python/vyos/ifconfig/vrrp.py | 19 | ||||
-rw-r--r-- | python/vyos/ifconfig/vti.py | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig/vxlan.py | 68 | ||||
-rw-r--r-- | python/vyos/ifconfig/wireguard.py | 4 |
21 files changed, 544 insertions, 199 deletions
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index a37615c8f..206b2bba1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2022 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 @@ -36,4 +36,6 @@ from vyos.ifconfig.tunnel import TunnelIf from vyos.ifconfig.wireless import WiFiIf from vyos.ifconfig.l2tpv3 import L2TPv3If from vyos.ifconfig.macsec import MACsecIf +from vyos.ifconfig.veth import VethIf from vyos.ifconfig.wwan import WWANIf +from vyos.ifconfig.sstpc import SSTPCIf diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 0edd17055..d1d7d48c4 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -16,10 +16,10 @@ import os from vyos.ifconfig.interface import Interface -from vyos.util import cmd -from vyos.util import dict_search -from vyos.validate import assert_list -from vyos.validate import assert_positive +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search +from vyos.utils.assertion import assert_list +from vyos.utils.assertion import assert_positive @Interface.register class BondIf(Interface): diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index aa818bc5f..b29e71394 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -17,10 +17,10 @@ from netifaces import interfaces import json 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 dict_search +from vyos.utils.assertion import assert_boolean +from vyos.utils.assertion import assert_positive +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search from vyos.configdict import get_vlan_ids from vyos.configdict import list_diff diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index 7a6b36e7c..7402da55a 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 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 @@ -19,10 +19,10 @@ from inspect import signature from inspect import _empty from vyos.ifconfig.section import Section -from vyos.util import popen -from vyos.util import cmd -from vyos.util import read_file -from vyos.util import write_file +from vyos.utils.process import popen +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.file import write_file from vyos import debug class Control(Section): @@ -49,6 +49,18 @@ class Control(Section): return popen(command, self.debug) def _cmd(self, command): + import re + if 'netns' in self.config: + # This command must be executed from default netns 'ip link set dev X netns X' + # exclude set netns cmd from netns to avoid: + # failed to run command: ip netns exec ns01 ip link set dev veth20 netns ns01 + pattern = r'ip link set dev (\S+) netns (\S+)' + matches = re.search(pattern, command) + if matches and matches.group(2) == self.config['netns']: + # Command already includes netns and matches desired namespace: + command = command + else: + command = f'ip netns exec {self.config["netns"]} {command}' return cmd(command, self.debug) def _get_command(self, config, name): diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 519cfc58c..24ce3a803 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 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 @@ -14,15 +14,16 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. import os -import re from glob import glob + +from vyos.base import Warning from vyos.ethtool import Ethtool from vyos.ifconfig.interface import Interface -from vyos.util import run -from vyos.util import dict_search -from vyos.util import read_file -from vyos.validate import assert_list +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file +from vyos.utils.process import run +from vyos.utils.assertion import assert_list @Interface.register class EthernetIf(Interface): @@ -118,7 +119,7 @@ class EthernetIf(Interface): cmd = f'ethtool --pause {ifname} autoneg {enable} tx {enable} rx {enable}' output, code = self._popen(cmd) if code: - print(f'Could not set flowcontrol for {ifname}') + Warning(f'could not change "{ifname}" flow control setting!') return output return None @@ -134,6 +135,7 @@ class EthernetIf(Interface): >>> i = EthernetIf('eth0') >>> i.set_speed_duplex('auto', 'auto') """ + ifname = self.config['ifname'] if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', '25000', '40000', '50000', '100000', '400000']: @@ -143,7 +145,11 @@ class EthernetIf(Interface): raise ValueError("Value out of range (duplex)") if not self.ethtool.check_speed_duplex(speed, duplex): - self._debug_msg(f'NIC driver does not support changing speed/duplex settings!') + Warning(f'changing speed/duplex setting on "{ifname}" is unsupported!') + return + + if not self.ethtool.check_auto_negotiation_supported(): + Warning(f'changing auto-negotiation setting on "{ifname}" is unsupported!') return # Get current speed and duplex settings: @@ -239,7 +245,7 @@ class EthernetIf(Interface): if not isinstance(state, bool): raise ValueError('Value out of range') - rps_cpus = '0' + rps_cpus = 0 queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) if state: # Enable RPS on all available CPUs except CPU0 which we will not @@ -248,10 +254,16 @@ class EthernetIf(Interface): # representation of the CPUs which should participate on RPS, we # can enable more CPUs that are physically present on the system, # Linux will clip that internally! - rps_cpus = 'ffffffff,ffffffff,ffffffff,fffffffe' + rps_cpus = (1 << os.cpu_count()) -1 + + # XXX: we should probably reserve one core when the system is under + # high preasure so we can still have a core left for housekeeping. + # This is done by masking out the lowst bit so CPU0 is spared from + # receive packet steering. + rps_cpus &= ~1 for i in range(0, queues): - self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus) + self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}') # send bitmask representation as hex string without leading '0x' return True @@ -362,10 +374,11 @@ class EthernetIf(Interface): self.set_tso(dict_search('offload.tso', config) != None) # Set physical interface speed and duplex - if {'speed', 'duplex'} <= set(config): - speed = config.get('speed') - duplex = config.get('duplex') - self.set_speed_duplex(speed, duplex) + if 'speed_duplex_changed' in config: + if {'speed', 'duplex'} <= set(config): + speed = config.get('speed') + duplex = config.get('duplex') + self.set_speed_duplex(speed, duplex) # Set interface ring buffer if 'ring_buffer' in config: diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py index 276c34cd7..fbb261a35 100644 --- a/python/vyos/ifconfig/geneve.py +++ b/python/vyos/ifconfig/geneve.py @@ -14,7 +14,7 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.ifconfig import Interface -from vyos.util import dict_search +from vyos.utils.dict import dict_search @Interface.register class GeneveIf(Interface): @@ -45,6 +45,7 @@ class GeneveIf(Interface): 'parameters.ip.df' : 'df', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', + 'parameters.ip.innerproto' : 'innerprotoinherit', 'parameters.ipv6.flowlabel' : 'flowlabel', } diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py index db7d2b6b4..3e5f5790d 100644 --- a/python/vyos/ifconfig/input.py +++ b/python/vyos/ifconfig/input.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 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 @@ -17,6 +17,16 @@ from vyos.ifconfig.interface import Interface @Interface.register class InputIf(Interface): + """ + The Intermediate Functional Block (ifb) pseudo network interface acts as a + QoS concentrator for multiple different sources of traffic. Packets from + or to other interfaces have to be redirected to it using the mirred action + in order to be handled, regularly routed traffic will be dropped. This way, + a single stack of qdiscs, classes and filters can be shared between + multiple interfaces. + """ + + iftype = 'ifb' definition = { **Interface.definition, **{ 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}' diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py index fcd1fbf81..85a89ef8b 100644 --- a/python/vyos/ifconfig/l2tpv3.py +++ b/python/vyos/ifconfig/l2tpv3.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 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 @@ -15,7 +15,8 @@ from time import sleep from time import time -from vyos.util import run + +from vyos.utils.process import run from vyos.ifconfig.interface import Interface def wait_for_add_l2tpv3(timeout=10, sleep_interval=1, cmd=None): diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index b3babfadc..e1d041839 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -46,7 +46,7 @@ class LoopbackIf(Interface): if addr in self._persistent_addresses: # Do not allow deletion of the default loopback addresses as # this will cause weird system behavior like snmp/ssh no longer - # operating as expected, see https://phabricator.vyos.net/T2034. + # operating as expected, see https://vyos.dev/T2034. continue self.del_addr(addr) diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index 1a78d18d8..9329c5ee7 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -41,10 +41,30 @@ class MACsecIf(Interface): Create MACsec interface in OS kernel. Interface is administrative down by default. """ + # create tunnel interface cmd = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config) cmd += f' cipher {self.config["security"]["cipher"]}' self._cmd(cmd) + # Check if using static keys + if 'static' in self.config["security"]: + # Set static TX key + cmd = 'ip macsec add {ifname} tx sa 0 pn 1 on key 00'.format(**self.config) + cmd += f' {self.config["security"]["static"]["key"]}' + self._cmd(cmd) + + for peer, peer_config in self.config["security"]["static"]["peer"].items(): + if 'disable' in peer_config: + continue + + # Create the address + cmd = 'ip macsec add {ifname} rx port 1 address'.format(**self.config) + cmd += f' {peer_config["mac"]}' + self._cmd(cmd) + # Add the rx-key to the address + cmd += f' sa 0 pn 1 on key 01 {peer_config["key"]}' + self._cmd(cmd) + # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index 776014bc3..2266879ec 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2022 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 @@ -30,10 +30,17 @@ class MACVLANIf(Interface): } def _create(self): + """ + Create MACvlan interface in OS kernel. Interface is administrative + down by default. + """ # please do not change the order when assembling the command cmd = 'ip link add {ifname} link {source_interface} type {type} mode {mode}' self._cmd(cmd.format(**self.config)) + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') + def set_mode(self, mode): ifname = self.config['ifname'] cmd = f'ip link set dev {ifname} type macvlan mode {mode}' diff --git a/python/vyos/ifconfig/operational.py b/python/vyos/ifconfig/operational.py index 33e8614f0..dc2742123 100644 --- a/python/vyos/ifconfig/operational.py +++ b/python/vyos/ifconfig/operational.py @@ -143,15 +143,17 @@ class Operational(Control): except IOError: return no_stats - def clear_counters(self, counters=None): - clear = self._stats_all if counters is None else [] - stats = self.load_counters() + def clear_counters(self): + stats = self.get_stats() for counter, value in stats.items(): - stats[counter] = 0 if counter in clear else value + stats[counter] = value self.save_counters(stats) def reset_counters(self): - os.remove(self.cachefile(self.ifname)) + try: + os.remove(self.cachefile(self.ifname)) + except FileNotFoundError: + pass def get_stats(self): """ return a dict() with the value for each interface counter """ diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 437fe0cae..febf1452d 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -14,8 +14,8 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.ifconfig.interface import Interface -from vyos.validate import assert_range -from vyos.util import get_interface_config +from vyos.utils.assertion import assert_range +from vyos.utils.network import get_interface_config @Interface.register class PPPoEIf(Interface): diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py new file mode 100644 index 000000000..50fc6ee6b --- /dev/null +++ b/python/vyos/ifconfig/sstpc.py @@ -0,0 +1,40 @@ +# Copyright 2022 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# 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/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class SSTPCIf(Interface): + iftype = 'sstpc' + definition = { + **Interface.definition, + **{ + 'section': 'sstpc', + 'prefixes': ['sstpc', ], + 'eternal': 'sstpc[0-9]+$', + }, + } + + def _create(self): + # we can not create this interface as it is managed outside + pass + + def _delete(self): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 5258a2cb1..9ba7b31a6 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -17,8 +17,8 @@ # https://community.hetzner.com/tutorials/linux-setup-gre-tunnel from vyos.ifconfig.interface import Interface -from vyos.util import dict_search -from vyos.validate import assert_list +from vyos.utils.dict import dict_search +from vyos.utils.assertion import assert_list def enable_to_on(value): if value == 'enable': @@ -83,11 +83,6 @@ class TunnelIf(Interface): 'convert': enable_to_on, 'shellcmd': 'ip link set dev {ifname} multicast {value}', }, - 'allmulticast': { - 'validate': lambda v: assert_list(v, ['enable', 'disable']), - 'convert': enable_to_on, - 'shellcmd': 'ip link set dev {ifname} allmulticast {value}', - }, } } @@ -162,6 +157,10 @@ class TunnelIf(Interface): """ Get a synthetic MAC address. """ return self.get_mac_synthetic() + def set_multicast(self, enable): + """ Change the MULTICAST flag on the device """ + return self.set_interface('multicast', enable) + 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 @@ -170,5 +169,10 @@ class TunnelIf(Interface): # Adjust iproute2 tunnel parameters if necessary self._change_options() + # IP Multicast + tmp = dict_search('enable_multicast', config) + value = 'enable' if (tmp != None) else 'disable' + self.set_multicast(value) + # call base class first super().update(config) diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py new file mode 100644 index 000000000..aafbf226a --- /dev/null +++ b/python/vyos/ifconfig/veth.py @@ -0,0 +1,54 @@ +# Copyright 2022 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# 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/>. + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class VethIf(Interface): + """ + Abstraction of a Linux veth interface + """ + iftype = 'veth' + definition = { + **Interface.definition, + **{ + 'section': 'virtual-ethernet', + 'prefixes': ['veth', ], + 'bridgeable': True, + }, + } + + def _create(self): + """ + Create veth interface in OS kernel. Interface is administrative + down by default. + """ + # check before create, as we have 2 veth interfaces in our CLI + # interface virtual-ethernet veth0 peer-name 'veth1' + # interface virtual-ethernet veth1 peer-name 'veth0' + # + # but iproute2 creates the pair with one command: + # ip link add vet0 type veth peer name veth1 + if self.exists(self.config['peer_name']): + return + + # create virtual-ethernet interface + cmd = 'ip link add {ifname} type {type}'.format(**self.config) + cmd += f' peer name {self.config["peer_name"]}' + self._cmd(cmd) + + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index 47aaadecd..fde903a53 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -21,8 +21,11 @@ from time import time from time import sleep from tabulate import tabulate -from vyos import util from vyos.configquery import ConfigTreeQuery +from vyos.utils.convert import seconds_to_human +from vyos.utils.file import read_file +from vyos.utils.file import wait_for_file_write_complete +from vyos.utils.process import process_running class VRRPError(Exception): pass @@ -84,21 +87,21 @@ class VRRP(object): def is_running(cls): if not os.path.exists(cls.location['pid']): return False - return util.process_running(cls.location['pid']) + return process_running(cls.location['pid']) @classmethod def collect(cls, what): fname = cls.location[what] try: # send signal to generate the configuration file - pid = util.read_file(cls.location['pid']) - util.wait_for_file_write_complete(fname, + pid = read_file(cls.location['pid']) + wait_for_file_write_complete(fname, pre_hook=(lambda: os.kill(int(pid), cls._signal[what])), timeout=30) - return util.read_file(fname) + return read_file(fname) except OSError: - # raised by vyos.util.read_file + # raised by vyos.utils.file.read_file raise VRRPNoData("VRRP data is not available (wait time exceeded)") except FileNotFoundError: raise VRRPNoData("VRRP data is not available (process not running or no active groups)") @@ -145,7 +148,7 @@ class VRRP(object): priority = data['effective_priority'] since = int(time() - float(data['last_transition'])) - last = util.seconds_to_human(since) + last = seconds_to_human(since) groups.append([name, intf, vrid, state, priority, last]) diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py index dc99d365a..9ebbeb9ed 100644 --- a/python/vyos/ifconfig/vti.py +++ b/python/vyos/ifconfig/vti.py @@ -14,7 +14,7 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.ifconfig.interface import Interface -from vyos.util import dict_search +from vyos.utils.dict import dict_search @Interface.register class VTIIf(Interface): diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 5baff10a9..1fe5db7cd 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.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 @@ -13,9 +13,15 @@ # 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/>. +from json import loads + from vyos import ConfigError +from vyos.configdict import list_diff from vyos.ifconfig import Interface -from vyos.util import dict_search +from vyos.utils.assertion import assert_list +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vxlan_vlan_tunnels @Interface.register class VXLANIf(Interface): @@ -49,6 +55,13 @@ class VXLANIf(Interface): } } + _command_set = {**Interface._command_set, **{ + 'vlan_tunnel': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}', + }, + }} + def _create(self): # This table represents a mapping from VyOS internal config dict to # arguments used by iproute2. For more information please refer to: @@ -99,3 +112,54 @@ class VXLANIf(Interface): cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ 'port {port} dev {ifname}' self._cmd(cmd.format(**self.config)) + + def set_vlan_vni_mapping(self, state): + """ + Controls whether vlan to tunnel mapping is enabled on the port. + By default this flag is off. + """ + if not isinstance(state, bool): + raise ValueError('Value out of range') + + cur_vlan_ids = [] + if 'vlan_to_vni_removed' in self.config: + cur_vlan_ids = self.config['vlan_to_vni_removed'] + for vlan in cur_vlan_ids: + self._cmd(f'bridge vlan del dev {self.ifname} vid {vlan}') + + # Determine current OS Kernel vlan_tunnel setting - only adjust when needed + tmp = get_interface_config(self.ifname) + cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.vlan_tunnel', tmp) == True else 'off' + new_state = 'on' if state else 'off' + if cur_state != new_state: + self.set_interface('vlan_tunnel', new_state) + + # Determine current OS Kernel configured VLANs + os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname) + + if 'vlan_to_vni' in self.config: + add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids) + + for vlan, vlan_config in self.config['vlan_to_vni'].items(): + # VLAN mapping already exists - skip + if vlan not in add_vlan: + continue + + vni = vlan_config['vni'] + # The following commands must be run one after another, + # they can not be combined with linux 6.1 and iproute2 6.1 + self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan}') + self._cmd(f'bridge vlan add dev {self.ifname} vid {vlan} tunnel_info id {vni}') + + 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 last + super().update(config) + + # Enable/Disable VLAN tunnel mapping + # This is only possible after the interface was assigned to the bridge + self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None) diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index fe5e9c519..4aac103ec 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.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 @@ -25,6 +25,7 @@ from hurry.filesize import alternative from vyos.ifconfig import Interface from vyos.ifconfig import Operational from vyos.template import is_ipv6 +from vyos.base import Warning class WireGuardOperational(Operational): def _dump(self): @@ -184,7 +185,6 @@ class WireGuardIf(Interface): base_cmd += f' private-key {tmp_file.name}' base_cmd = base_cmd.format(**config) - if 'peer' in config: for peer, peer_config in config['peer'].items(): # T4702: No need to configure this peer when it was explicitly |