summaryrefslogtreecommitdiff
path: root/python/vyos/ifconfig
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/ifconfig')
-rw-r--r--python/vyos/ifconfig/__init__.py4
-rw-r--r--python/vyos/ifconfig/bond.py8
-rw-r--r--python/vyos/ifconfig/bridge.py8
-rw-r--r--python/vyos/ifconfig/control.py22
-rw-r--r--python/vyos/ifconfig/ethernet.py43
-rw-r--r--python/vyos/ifconfig/geneve.py3
-rw-r--r--python/vyos/ifconfig/input.py12
-rw-r--r--python/vyos/ifconfig/interface.py384
-rw-r--r--python/vyos/ifconfig/l2tpv3.py5
-rw-r--r--python/vyos/ifconfig/loopback.py2
-rw-r--r--python/vyos/ifconfig/macsec.py22
-rw-r--r--python/vyos/ifconfig/macvlan.py9
-rw-r--r--python/vyos/ifconfig/operational.py12
-rw-r--r--python/vyos/ifconfig/pppoe.py4
-rw-r--r--python/vyos/ifconfig/sstpc.py40
-rw-r--r--python/vyos/ifconfig/tunnel.py18
-rw-r--r--python/vyos/ifconfig/veth.py54
-rw-r--r--python/vyos/ifconfig/vrrp.py19
-rw-r--r--python/vyos/ifconfig/vti.py2
-rw-r--r--python/vyos/ifconfig/vxlan.py68
-rw-r--r--python/vyos/ifconfig/wireguard.py4
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