summaryrefslogtreecommitdiff
path: root/python/vyos/ifconfig/interface.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/ifconfig/interface.py')
-rwxr-xr-xpython/vyos/ifconfig/interface.py296
1 files changed, 254 insertions, 42 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index a1928ba51..e6dbd861b 100755
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -37,7 +37,9 @@ 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 is_systemd_service_active
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
@@ -52,6 +54,9 @@ from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
from vyos.ifconfig import Section
+from netaddr import EUI
+from netaddr import mac_unix_expanded
+
class Interface(Control):
# This is the class which will be used to create
# self.operational, it allows subclasses, such as
@@ -103,6 +108,10 @@ class Interface(Control):
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].operstate | [0]', json.loads(j)),
},
+ 'vrf': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].master | [0]', json.loads(j)),
+ },
}
_command_set = {
@@ -134,7 +143,6 @@ class Interface(Control):
_sysfs_set = {
'arp_cache_tmo': {
- 'convert': lambda tmo: (int(tmo) * 1000),
'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms',
},
'arp_filter': {
@@ -204,6 +212,51 @@ class Interface(Control):
},
}
+ _sysfs_get = {
+ 'arp_cache_tmo': {
+ 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms',
+ },
+ 'arp_filter': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_filter',
+ },
+ 'arp_accept': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_accept',
+ },
+ 'arp_announce': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_announce',
+ },
+ 'arp_ignore': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore',
+ },
+ 'ipv4_forwarding': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding',
+ },
+ 'rp_filter': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
+ },
+ 'ipv6_accept_ra': {
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
+ },
+ 'ipv6_autoconf': {
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/autoconf',
+ },
+ 'ipv6_forwarding': {
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding',
+ },
+ 'ipv6_dad_transmits': {
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits',
+ },
+ 'proxy_arp': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp',
+ },
+ 'proxy_arp_pvlan': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp_pvlan',
+ },
+ 'link_detect': {
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
+ },
+ }
+
@classmethod
def exists(cls, ifname):
return os.path.exists(f'/sys/class/net/{ifname}')
@@ -322,9 +375,7 @@ class Interface(Control):
'info_data', {}).get('table')
# Add map element with interface and zone ID
if vrf_table_id:
- self._cmd(
- f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}'
- )
+ self._cmd(f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}')
else:
nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
# Check if deleting is possible first to avoid raising errors
@@ -376,6 +427,9 @@ class Interface(Control):
>>> Interface('eth0').get_mtu()
'1400'
"""
+ tmp = self.get_interface('mtu')
+ if str(tmp) == mtu:
+ return None
return self.set_interface('mtu', mtu)
def get_mac(self):
@@ -389,6 +443,47 @@ class Interface(Control):
"""
return self.get_interface('mac')
+ def get_mac_synthetic(self):
+ """
+ Get a synthetic MAC address. This is a common method which can be called
+ from derived classes to overwrite the get_mac() call in a generic way.
+
+ NOTE: Tunnel interfaces have no "MAC" address by default. The content
+ of the 'address' file in /sys/class/net/device contains the
+ local-ip thus we generate a random MAC address instead
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_mac()
+ '00:50:ab:cd:ef:00'
+ """
+ from hashlib import sha256
+
+ # Get processor ID number
+ cpu_id = self._cmd('sudo dmidecode -t 4 | grep ID | head -n1 | sed "s/.*ID://;s/ //g"')
+ # Get system eth0 base MAC address - every system has eth0
+ eth0_mac = Interface('eth0').get_mac()
+
+ sha = sha256()
+ # Calculate SHA256 sum based on the CPU ID number, eth0 mac address and
+ # this interface identifier - this is as predictable as an interface
+ # MAC address and thus can be used in the same way
+ sha.update(cpu_id.encode())
+ sha.update(eth0_mac.encode())
+ sha.update(self.ifname.encode())
+ # take the most significant 48 bits from the SHA256 string
+ tmp = sha.hexdigest()[:12]
+ # Convert pseudo random string into EUI format which now represents a
+ # MAC address
+ tmp = EUI(tmp).value
+ # set locally administered bit in MAC address
+ tmp |= 0xf20000000000
+ # convert integer to "real" MAC address representation
+ mac = EUI(hex(tmp).split('x')[-1])
+ # change dialect to use : as delimiter instead of -
+ mac.dialect = mac_unix_expanded
+ return str(mac)
+
def set_mac(self, mac):
"""
Set interface MAC (Media Access Contrl) address to given value.
@@ -413,7 +508,7 @@ class Interface(Control):
if prev_state == 'up':
self.set_admin_state('up')
- def set_vrf(self, vrf=''):
+ def set_vrf(self, vrf):
"""
Add/Remove interface from given VRF instance.
@@ -422,6 +517,11 @@ class Interface(Control):
>>> Interface('eth0').set_vrf('foo')
>>> Interface('eth0').set_vrf()
"""
+
+ tmp = self.get_interface('vrf')
+ if tmp == vrf:
+ return None
+
self.set_interface('vrf', vrf)
self._set_vrf_ct_zone(vrf)
@@ -434,8 +534,68 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_arp_cache_tmo(40)
"""
+ tmo = str(int(tmo) * 1000)
+ tmp = self.get_interface('arp_cache_tmo')
+ if tmp == tmo:
+ return None
return self.set_interface('arp_cache_tmo', tmo)
+ def set_tcp_ipv4_mss(self, mss):
+ """
+ Set IPv4 TCP MSS value advertised when TCP SYN packets leave this
+ interface. Value is in bytes.
+
+ A value of 0 will disable the MSS adjustment
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_tcp_ipv4_mss(1340)
+ """
+ iptables_bin = 'iptables'
+ base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN'
+ out = self._cmd(f'{iptables_bin}-save -t mangle')
+ for line in out.splitlines():
+ if line.startswith(base_options):
+ # remove OLD MSS mangling configuration
+ line = line.replace('-A FORWARD', '-D FORWARD')
+ self._cmd(f'{iptables_bin} -t mangle {line}')
+
+ cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS'
+ if mss == 'clamp-mss-to-pmtu':
+ self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu')
+ elif int(mss) > 0:
+ # probably add option to clamp only if bigger:
+ low_mss = str(int(mss) + 1)
+ self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}')
+
+ def set_tcp_ipv6_mss(self, mss):
+ """
+ Set IPv6 TCP MSS value advertised when TCP SYN packets leave this
+ interface. Value is in bytes.
+
+ A value of 0 will disable the MSS adjustment
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_tcp_mss(1320)
+ """
+ iptables_bin = 'ip6tables'
+ base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN'
+ out = self._cmd(f'{iptables_bin}-save -t mangle')
+ for line in out.splitlines():
+ if line.startswith(base_options):
+ # remove OLD MSS mangling configuration
+ line = line.replace('-A FORWARD', '-D FORWARD')
+ self._cmd(f'{iptables_bin} -t mangle {line}')
+
+ cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS'
+ if mss == 'clamp-mss-to-pmtu':
+ self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu')
+ elif int(mss) > 0:
+ # probably add option to clamp only if bigger:
+ low_mss = str(int(mss) + 1)
+ self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}')
+
def set_arp_filter(self, arp_filter):
"""
Filter ARP requests
@@ -454,6 +614,9 @@ class Interface(Control):
particular interfaces. Only for more complex setups like load-
balancing, does this behaviour cause problems.
"""
+ tmp = self.get_interface('arp_filter')
+ if tmp == arp_filter:
+ return None
return self.set_interface('arp_filter', arp_filter)
def set_arp_accept(self, arp_accept):
@@ -470,6 +633,9 @@ class Interface(Control):
gratuitous arp frame, the arp table will be updated regardless
if this setting is on or off.
"""
+ tmp = self.get_interface('arp_accept')
+ if tmp == arp_accept:
+ return None
return self.set_interface('arp_accept', arp_accept)
def set_arp_announce(self, arp_announce):
@@ -491,6 +657,9 @@ class Interface(Control):
receiving answer from the resolved target while decreasing
the level announces more valid sender's information.
"""
+ tmp = self.get_interface('arp_announce')
+ if tmp == arp_announce:
+ return None
return self.set_interface('arp_announce', arp_announce)
def set_arp_ignore(self, arp_ignore):
@@ -503,12 +672,16 @@ class Interface(Control):
1 - reply only if the target IP address is local address
configured on the incoming interface
"""
+ tmp = self.get_interface('arp_ignore')
+ if tmp == arp_ignore:
+ return None
return self.set_interface('arp_ignore', arp_ignore)
def set_ipv4_forwarding(self, forwarding):
- """
- Configure IPv4 forwarding.
- """
+ """ Configure IPv4 forwarding. """
+ tmp = self.get_interface('ipv4_forwarding')
+ if tmp == forwarding:
+ return None
return self.set_interface('ipv4_forwarding', forwarding)
def set_ipv4_source_validation(self, value):
@@ -537,6 +710,9 @@ class Interface(Control):
print(f'WARNING: Global source-validation is set to "{global_setting}\n"' \
'this overrides per interface setting!')
+ tmp = self.get_interface('rp_filter')
+ if int(tmp) == value:
+ return None
return self.set_interface('rp_filter', value)
def set_ipv6_accept_ra(self, accept_ra):
@@ -552,6 +728,9 @@ class Interface(Control):
2 - Overrule forwarding behaviour. Accept Router Advertisements even if
forwarding is enabled.
"""
+ tmp = self.get_interface('ipv6_accept_ra')
+ if tmp == accept_ra:
+ return None
return self.set_interface('ipv6_accept_ra', accept_ra)
def set_ipv6_autoconf(self, autoconf):
@@ -559,6 +738,9 @@ class Interface(Control):
Autoconfigure addresses using Prefix Information in Router
Advertisements.
"""
+ tmp = self.get_interface('ipv6_autoconf')
+ if tmp == autoconf:
+ return None
return self.set_interface('ipv6_autoconf', autoconf)
def add_ipv6_eui64_address(self, prefix):
@@ -582,9 +764,10 @@ class Interface(Control):
Delete the address based on the interface's MAC-based EUI64
combined with the prefix address.
"""
- eui64 = mac2eui64(self.get_mac(), prefix)
- prefixlen = prefix.split('/')[1]
- self.del_addr(f'{eui64}/{prefixlen}')
+ if is_ipv6(prefix):
+ eui64 = mac2eui64(self.get_mac(), prefix)
+ prefixlen = prefix.split('/')[1]
+ self.del_addr(f'{eui64}/{prefixlen}')
def set_ipv6_forwarding(self, forwarding):
"""
@@ -611,6 +794,9 @@ class Interface(Control):
3. Router Advertisements are ignored unless accept_ra is 2.
4. Redirects are ignored.
"""
+ tmp = self.get_interface('ipv6_forwarding')
+ if tmp == forwarding:
+ return None
return self.set_interface('ipv6_forwarding', forwarding)
def set_ipv6_dad_messages(self, dad):
@@ -618,6 +804,9 @@ class Interface(Control):
The amount of Duplicate Address Detection probes to send.
Default: 1
"""
+ tmp = self.get_interface('ipv6_dad_transmits')
+ if tmp == dad:
+ return None
return self.set_interface('ipv6_dad_transmits', dad)
def set_link_detect(self, link_filter):
@@ -640,6 +829,9 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_link_detect(1)
"""
+ tmp = self.get_interface('link_detect')
+ if tmp == link_filter:
+ return None
return self.set_interface('link_detect', link_filter)
def get_alias(self):
@@ -664,6 +856,9 @@ class Interface(Control):
>>> Interface('eth0').set_ifalias('')
"""
+ tmp = self.get_interface('alias')
+ if tmp == ifalias:
+ return None
self.set_interface('alias', ifalias)
def get_admin_state(self):
@@ -739,6 +934,9 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_proxy_arp(1)
"""
+ tmp = self.get_interface('proxy_arp')
+ if tmp == enable:
+ return None
self.set_interface('proxy_arp', enable)
def set_proxy_arp_pvlan(self, enable):
@@ -765,6 +963,9 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_proxy_arp_pvlan(1)
"""
+ tmp = self.get_interface('proxy_arp_pvlan')
+ if tmp == enable:
+ return None
self.set_interface('proxy_arp_pvlan', enable)
def get_addr_v4(self):
@@ -899,6 +1100,8 @@ class Interface(Control):
>>> j.get_addr()
['2001:db8::ffff/64']
"""
+ if not addr:
+ raise ValueError()
# remove from interface
if addr == 'dhcp':
@@ -1005,7 +1208,9 @@ class Interface(Control):
lease_file = f'{config_base}_{ifname}.leases'
# Stop client with old config files to get the right IF_METRIC.
- self._cmd(f'systemctl stop dhclient@{ifname}.service')
+ systemd_service = f'dhclient@{ifname}.service'
+ if is_systemd_service_active(systemd_service):
+ self._cmd(f'systemctl stop {systemd_service}')
if enable and 'disable' not in self._config:
if dict_search('dhcp_options.host_name', self._config) == None:
@@ -1025,7 +1230,7 @@ class Interface(Control):
# '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.
- return self._cmd(f'systemctl start dhclient@{ifname}.service')
+ return self._cmd(f'systemctl restart {systemd_service}')
else:
# cleanup old config files
for file in [config_file, options_file, pid_file, lease_file]:
@@ -1042,17 +1247,18 @@ class Interface(Control):
ifname = self.ifname
config_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf'
+ systemd_service = f'dhcp6c@{ifname}.service'
if enable and 'disable' not in self._config:
render(config_file, 'dhcp-client/ipv6.tmpl',
self._config)
- # We must ignore any return codes. This is required to enable DHCPv6-PD
- # for interfaces which are yet not up and running.
- return self._popen(f'systemctl restart dhcp6c@{ifname}.service')
+ # We must ignore any return codes. This is required to enable
+ # DHCPv6-PD for interfaces which are yet not up and running.
+ return self._popen(f'systemctl restart {systemd_service}')
else:
- self._popen(f'systemctl stop dhcp6c@{ifname}.service')
-
+ if is_systemd_service_active(systemd_service):
+ self._cmd(f'systemctl stop {systemd_service}')
if os.path.isfile(config_file):
os.remove(config_file)
@@ -1069,12 +1275,14 @@ class Interface(Control):
source_if = next(iter(self._config['is_mirror_intf']))
config = self._config['is_mirror_intf'][source_if].get('mirror', None)
- # Please do not clear the 'set $? = 0 '. It's meant to force a return of 0
- # Remove existing mirroring rules
- delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress 2> /dev/null;'
- delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio 2> /dev/null;'
- delete_tc_cmd += 'set $?=0'
- self._popen(delete_tc_cmd)
+ # Check configuration stored by old perl code before delete T3782
+ if not 'redirect' in self._config:
+ # Please do not clear the 'set $? = 0 '. It's meant to force a return of 0
+ # Remove existing mirroring rules
+ delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress 2> /dev/null;'
+ delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio 2> /dev/null;'
+ delete_tc_cmd += 'set $?=0'
+ self._popen(delete_tc_cmd)
# Bail out early if nothing needs to be configured
if not config:
@@ -1097,7 +1305,6 @@ class Interface(Control):
mirror_cmd += f'tc filter add dev {source_if} parent {parent} protocol all prio 10 u32 match u32 0 0 flowid 1:1 action mirred egress mirror dev {mirror_if}'
self._popen(mirror_cmd)
-
def set_xdp(self, state):
"""
Enable Kernel XDP support. State can be either True or False.
@@ -1174,16 +1381,16 @@ class Interface(Control):
# determine IP addresses which are assigned to the interface and build a
# list of addresses which are no longer in the dict so they can be removed
- cur_addr = self.get_addr()
- for addr in list_diff(cur_addr, new_addr):
- # 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')
- if addr != f'{eui64}/64':
+ if 'address_old' in config:
+ for addr in list_diff(config['address_old'], new_addr):
+ # 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')
+ if addr != f'{eui64}/64':
+ self.del_addr(addr)
+ else:
self.del_addr(addr)
- else:
- self.del_addr(addr)
for addr in new_addr:
self.add_addr(addr)
@@ -1202,6 +1409,16 @@ class Interface(Control):
# checked before
self.set_vrf(config.get('vrf', ''))
+ # Configure MSS value for IPv4 TCP connections
+ tmp = dict_search('ip.adjust_mss', config)
+ value = tmp if (tmp != None) else '0'
+ self.set_tcp_ipv4_mss(value)
+
+ # Configure MSS value for IPv6 TCP connections
+ tmp = dict_search('ipv6.adjust_mss', config)
+ value = tmp if (tmp != None) else '0'
+ self.set_tcp_ipv6_mss(value)
+
# Configure ARP cache timeout in milliseconds - has default value
tmp = dict_search('ip.arp_cache_timeout', config)
value = tmp if (tmp != None) else '30'
@@ -1274,16 +1491,11 @@ class Interface(Control):
self.set_mtu(config.get('mtu'))
# Delete old IPv6 EUI64 addresses before changing MAC
- tmp = dict_search('ipv6.address.eui64_old', config)
- if tmp:
- for addr in tmp:
- self.del_ipv6_eui64_address(addr)
+ for addr in (dict_search('ipv6.address.eui64_old', config) or []):
+ self.del_ipv6_eui64_address(addr)
# Manage IPv6 link-local addresses
- tmp = dict_search('ipv6.address.no_default_link_local', config)
- # we must check explicitly for None type as if the key is set we will
- # get an empty dict (<class 'dict'>)
- if isinstance(tmp, dict):
+ if dict_search('ipv6.address.no_default_link_local', config) != None:
self.del_ipv6_eui64_address('fe80::/64')
else:
self.add_ipv6_eui64_address('fe80::/64')