summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2023-05-30 09:04:34 +0000
committerViacheslav Hletenko <v.gletenko@vyos.io>2023-06-03 00:32:36 +0000
commite9c9d1a1e4f1bf876892b6e58f7288d36180889f (patch)
tree043f2f1f2087c4577479f0416a24c292a9149646 /python
parentaef116371f786f5d711731fdc712ae83dc4a34e2 (diff)
downloadvyos-1x-e9c9d1a1e4f1bf876892b6e58f7288d36180889f.tar.gz
vyos-1x-e9c9d1a1e4f1bf876892b6e58f7288d36180889f.zip
T5241: Support netns for veth and dummy interfaces
Add netns configuration for dummy and virtual-ethernet interfaces Change Interface class to get/set data to netns
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/control.py14
-rw-r--r--python/vyos/ifconfig/interface.py83
-rw-r--r--python/vyos/util.py8
-rw-r--r--python/vyos/validate.py68
4 files changed, 102 insertions, 71 deletions
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index 7a6b36e7c..915c1d2f9 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
@@ -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/interface.py b/python/vyos/ifconfig/interface.py
index 7754488c4..85fa90653 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
@@ -35,6 +35,7 @@ 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 run
from vyos.util import get_interface_config
from vyos.util import get_interface_namespace
from vyos.util import is_systemd_service_active
@@ -59,6 +60,15 @@ from netaddr import mac_unix_expanded
link_local_prefix = 'fe80::/64'
+
+def _interface_exists_in_netns(interface_name, netns):
+ from vyos.util import rc_cmd
+ rc, out = rc_cmd(f'ip netns exec {netns} ip link show dev {interface_name}')
+ if rc == 0:
+ return True
+ return False
+
+
class Interface(Control):
# This is the class which will be used to create
# self.operational, it allows subclasses, such as
@@ -137,9 +147,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}',
@@ -270,8 +277,11 @@ class Interface(Control):
}
@classmethod
- def exists(cls, ifname):
- return os.path.exists(f'/sys/class/net/{ifname}')
+ def exists(cls, ifname, netns=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):
@@ -339,7 +349,12 @@ 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 {self.config["netns"]} {cmd}'
self._cmd(cmd)
def remove(self):
@@ -374,6 +389,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.config['ifname'])
+ if netns: cmd = f'ip netns exec {netns} {cmd}'
return self._cmd(cmd)
def _set_vrf_ct_zone(self, vrf):
@@ -381,6 +399,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(
@@ -533,13 +555,9 @@ class Interface(Control):
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
-
# 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}')
+ if _interface_exists_in_netns(self.ifname, netns):
+ self._cmd(f'ip netns exec {netns} ip link del dev {self.ifname}')
return
def set_netns(self, netns):
@@ -551,7 +569,8 @@ class Interface(Control):
>>> Interface('dum0').set_netns('foo')
"""
- self.set_interface('netns', netns)
+ cmd = f'ip link set dev {self.ifname} netns {netns}'
+ self._cmd(cmd)
def set_vrf(self, vrf):
"""
@@ -586,6 +605,10 @@ class Interface(Control):
return self.set_interface('arp_cache_tmo', tmo)
def _cleanup_mss_rules(self, table, ifname):
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
commands = []
results = self._cmd(f'nft -a list chain {table} VYOS_TCP_MSS').split("\n")
for line in results:
@@ -605,6 +628,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'
@@ -739,6 +766,11 @@ class Interface(Control):
As per RFC3074.
"""
+
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
if value == 'strict':
value = 1
elif value == 'loose':
@@ -1101,8 +1133,9 @@ class Interface(Control):
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, self.config.get('netns')):
+ exec_within_netns = f'ip netns exec {self.config.get("netns")}' if self.config.get('netns') else ''
+ tmp = f'{exec_within_netns} ip addr add {addr} dev {self.ifname}'
# Add broadcast address for IPv4
if is_ipv4(addr): tmp += ' brd +'
@@ -1147,8 +1180,9 @@ class Interface(Control):
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=self.config.get('netns')):
+ exec_within_netns = f'ip netns exec {self.config.get("netns")}' if self.config.get('netns') else ''
+ self._cmd(f'{exec_within_netns} ip addr del "{addr}" dev "{self.ifname}"')
else:
return False
@@ -1168,8 +1202,12 @@ class Interface(Control):
self.set_dhcp(False)
self.set_dhcpv6(False)
+ _netns = get_interface_namespace(self.config['ifname'])
+ netns_exec = f'ip netns exec {_netns} ' if _netns else ''
+ cmd = netns_exec
+ cmd += f'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):
"""
@@ -1308,6 +1346,11 @@ class Interface(Control):
# - 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.
+
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return None
+
source_if = self._config['ifname']
mirror_config = None
@@ -1412,8 +1455,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 _interface_exists_in_netns(self.ifname, self.config['netns']):
+ self.set_netns(config.get('netns', ''))
else:
self.del_netns(config.get('netns', ''))
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 61ce59324..d83287fd2 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -906,14 +906,16 @@ def get_bridge_fdb(interface):
tmp = loads(cmd(f'bridge -j fdb show dev {interface}'))
return tmp
-def get_interface_config(interface):
+def get_interface_config(interface, netns=None):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
"""
- if not os.path.exists(f'/sys/class/net/{interface}'):
+ from vyos.util import run
+ netns_exec = f'ip netns exec {netns}' if netns else ''
+ if run(f'{netns_exec} test -e /sys/class/net/{interface}') != 0:
return None
from json import loads
- tmp = loads(cmd(f'ip -d -j link show {interface}'))[0]
+ tmp = loads(cmd(f'{netns_exec} ip -d -j link show {interface}'))[0]
return tmp
def get_interface_address(interface):
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index d18785aaf..d3e6e9087 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -43,57 +43,31 @@ def _are_same_ip(one, two):
s_two = AF_INET if is_ipv4(two) else AF_INET6
return inet_pton(f_one, one) == inet_pton(f_one, two)
-def is_intf_addr_assigned(intf, address) -> bool:
- """
- Verify if the given IPv4/IPv6 address is assigned to specific interface.
+def is_intf_addr_assigned(ifname, addr, netns=None):
+ """Verify if the given IPv4/IPv6 address is assigned to specific interface.
It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR
address 192.0.2.1/24.
"""
- from vyos.template import is_ipv4
-
- from netifaces import ifaddresses
- from netifaces import AF_INET
- from netifaces import AF_INET6
-
- # check if the requested address type is configured at all
- # {
- # 17: [{'addr': '08:00:27:d9:5b:04', 'broadcast': 'ff:ff:ff:ff:ff:ff'}],
- # 2: [{'addr': '10.0.2.15', 'netmask': '255.255.255.0', 'broadcast': '10.0.2.255'}],
- # 10: [{'addr': 'fe80::a00:27ff:fed9:5b04%eth0', 'netmask': 'ffff:ffff:ffff:ffff::'}]
- # }
- try:
- addresses = ifaddresses(intf)
- except ValueError as e:
- print(e)
- return False
-
- # determine IP version (AF_INET or AF_INET6) depending on passed address
- addr_type = AF_INET if is_ipv4(address) else AF_INET6
-
- # Check every IP address on this interface for a match
- netmask = None
- if '/' in address:
- address, netmask = address.split('/')
- for ip in addresses.get(addr_type, []):
- # ip can have the interface name in the 'addr' field, we need to remove it
- # {'addr': 'fe80::a00:27ff:fec5:f821%eth2', 'netmask': 'ffff:ffff:ffff:ffff::'}
- ip_addr = ip['addr'].split('%')[0]
-
- if not _are_same_ip(address, ip_addr):
- continue
-
- # we do not have a netmask to compare against, they are the same
- if not netmask:
- return True
-
- prefixlen = ''
- if is_ipv4(ip_addr):
- prefixlen = sum([bin(int(_)).count('1') for _ in ip['netmask'].split('.')])
- else:
- prefixlen = sum([bin(int(_,16)).count('1') for _ in ip['netmask'].split('/')[0].split(':') if _])
+ import json
+ import jmespath
+ from vyos.util import rc_cmd
+ from ipaddress import ip_interface
- if str(prefixlen) == netmask:
- return True
+ within_netns = f'ip netns exec {netns}' if netns else ''
+ rc, out = rc_cmd(f'{within_netns} ip --json address show dev {ifname}')
+ if rc == 0:
+ json_out = json.loads(out)
+ addresses = jmespath.search("[].addr_info[].{family: family, address: local, prefixlen: prefixlen}", json_out)
+ for address_info in addresses:
+ family = address_info['family']
+ address = address_info['address']
+ prefixlen = address_info['prefixlen']
+ # Remove the interface name if present in the given address
+ if '%' in addr:
+ addr = addr.split('%')[0]
+ interface = ip_interface(f"{address}/{prefixlen}")
+ if ip_interface(addr) == interface or address == addr:
+ return True
return False