From 667d83017590ef74590587d002c8775a0acfdd4f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 20 Aug 2021 16:19:10 +0200 Subject: vyos.ifconfig: bridge: remove missleading comment in update() (cherry picked from commit e1debb1b57a445fa2357f7dbb5b3f04383f8b1e3) --- python/vyos/ifconfig/bridge.py | 1 - 1 file changed, 1 deletion(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 65a4506c5..aadef0c09 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -366,5 +366,4 @@ class BridgeIf(Interface): self.set_vlan_filter(vlan_filter) - # call base class first super().update(config) -- cgit v1.2.3 From e6666353c41de8ad675d4157cdbe848a42ba6385 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 21 Aug 2021 20:57:17 +0200 Subject: vyos.ifconfig: provide generic get_mac_synthetic() method WireGuard, Tunnel and also PPPoE all need a ways to calculate a synthetic MAC address used for the EUI64 link-local addresses. Instead of copying the code from Tunnel to WireGuard to PPPoE, use a generic implementation. (cherry picked from commit b7d30137b17da49ed5099d4d96659b363fc7bcc9) --- python/vyos/ifconfig/interface.py | 29 +++++++++++++++++++++++++++++ python/vyos/ifconfig/tunnel.py | 26 ++------------------------ python/vyos/ifconfig/wireguard.py | 27 ++------------------------- 3 files changed, 33 insertions(+), 49 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 9c02af68f..e815c0129 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -52,6 +52,10 @@ 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 +from random import getrandbits + class Interface(Control): # This is the class which will be used to create # self.operational, it allows subclasses, such as @@ -367,6 +371,31 @@ 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' + """ + # we choose 40 random bytes for the MAC address, this gives + # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') + tmp = EUI(getrandbits(48)).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. diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index e40756cc7..5258a2cb1 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -16,10 +16,6 @@ # https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/ # https://community.hetzner.com/tutorials/linux-setup-gre-tunnel -from netaddr import EUI -from netaddr import mac_unix_expanded -from random import getrandbits - from vyos.ifconfig.interface import Interface from vyos.util import dict_search from vyos.validate import assert_list @@ -163,26 +159,8 @@ class TunnelIf(Interface): self._cmd(cmd.format(**self.config)) def get_mac(self): - """ - Get current interface MAC (Media Access Contrl) address used. - 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' - """ - # we choose 40 random bytes for the MAC address, this gives - # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') - tmp = EUI(getrandbits(48)).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) + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() def update(self, config): """ General helper function which works on a dictionary retrived by diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 2d2243b84..de1b56ce5 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -17,9 +17,6 @@ import os import time from datetime import timedelta -from netaddr import EUI -from netaddr import mac_unix_expanded -from random import getrandbits from hurry.filesize import size from hurry.filesize import alternative @@ -163,28 +160,8 @@ class WireGuardIf(Interface): 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] def get_mac(self): - """ - Get current interface MAC (Media Access Contrl) address used. - - 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' - """ - # we choose 40 random bytes for the MAC address, this gives - # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') - tmp = EUI(getrandbits(48)).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) + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() def update(self, config): """ General helper function which works on a dictionary retrived by -- cgit v1.2.3 From 785af7cf6603a81adc432537bf97987f59d818a3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 22 Aug 2021 15:13:48 +0200 Subject: bridge: T3137: backport vlan features from 1.4 current --- interface-definitions/interfaces-bridge.xml.in | 15 ++- python/vyos/ifconfig/bridge.py | 130 ++++++++++++------------ smoketest/scripts/cli/test_interfaces_bridge.py | 53 +++++++++- src/conf_mode/interfaces-bridge.py | 91 +++++------------ src/validators/allowed-vlan | 19 ++++ 5 files changed, 172 insertions(+), 136 deletions(-) create mode 100755 src/validators/allowed-vlan (limited to 'python/vyos/ifconfig') diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 91ce00ba6..ddfc5ade4 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -86,6 +86,12 @@ #include #include #include + + + Enable VLAN aware bridge + + + Interval at which neighbor bridges are removed @@ -138,7 +144,7 @@ VLAN id range allowed on this interface (use '-' as delimiter) - ^([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})$ + not a valid VLAN ID value or range @@ -172,6 +178,12 @@ 32 + + + Port is isolated (also known as Private-VLAN) + + + @@ -196,7 +208,6 @@ - #include #include diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index aadef0c09..27073b266 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -1,4 +1,4 @@ -# Copyright 2019 VyOS maintainers and contributors +# Copyright 2019-2021 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -22,6 +22,7 @@ from vyos.validate import assert_positive from vyos.util import cmd from vyos.util import dict_search from vyos.configdict import get_vlan_ids +from vyos.configdict import list_diff @Interface.register class BridgeIf(Interface): @@ -33,7 +34,6 @@ class BridgeIf(Interface): The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard. """ - iftype = 'bridge' definition = { **Interface.definition, @@ -267,21 +267,37 @@ class BridgeIf(Interface): for member in (tmp or []): if member in interfaces(): self.del_port(member) - vlan_filter = 0 - vlan_del = set() - vlan_add = set() + # enable/disable Vlan Filter + vlan_filter = '1' if 'enable_vlan' in config else '0' + self.set_vlan_filter(vlan_filter) ifname = config['ifname'] + if int(vlan_filter): + add_vlan = [] + cur_vlan_ids = get_vlan_ids(ifname) + + tmp = dict_search('vif', config) + if tmp: + for vif, vif_config in tmp.items(): + add_vlan.append(vif) + + # Remove redundant VLANs from the system + for vlan in list_diff(cur_vlan_ids, add_vlan): + cmd = f'bridge vlan del dev {ifname} vid {vlan} self' + self._cmd(cmd) + + for vlan in add_vlan: + cmd = f'bridge vlan add dev {ifname} vid {vlan} self' + self._cmd(cmd) + + # VLAN of bridge parent interface is always 1 + # VLAN 1 is the default VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {ifname} vid 1 pvid untagged self' + self._cmd(cmd) + tmp = dict_search('member.interface', config) if tmp: - if self.get_vlan_filter(): - bridge_vlan_ids = get_vlan_ids(ifname) - # Delete VLAN ID for the bridge - if 1 in bridge_vlan_ids: - bridge_vlan_ids.remove(1) - for vlan in bridge_vlan_ids: - vlan_del.add(str(vlan)) for interface, interface_config in tmp.items(): # if interface does yet not exist bail out early and @@ -296,9 +312,15 @@ class BridgeIf(Interface): # not have any addresses configured by CLI so just flush any # remaining ones lower.flush_addrs() + # enslave interface port to bridge self.add_port(interface) + # always set private-vlan/port isolation + tmp = dict_search('isolated', interface_config) + value = 'on' if (tmp != None) else 'off' + lower.set_port_isolation(value) + # set bridge port path cost if 'cost' in interface_config: value = interface_config.get('cost') @@ -309,61 +331,39 @@ class BridgeIf(Interface): value = interface_config.get('priority') lower.set_path_priority(value) - tmp = dict_search('native_vlan_removed', interface_config) - - for vlan_id in (tmp or []): - cmd = f'bridge vlan del dev {interface} vid {vlan_id}' - self._cmd(cmd) - cmd = f'bridge vlan add dev {interface} vid 1 pvid untagged master' - self._cmd(cmd) - vlan_del.add(vlan_id) - vlan_add.add(1) - - tmp = dict_search('allowed_vlan_removed', interface_config) - - for vlan_id in (tmp or []): - cmd = f'bridge vlan del dev {interface} vid {vlan_id}' - self._cmd(cmd) - vlan_del.add(vlan_id) - - if 'native_vlan' in interface_config: - vlan_filter = 1 - cmd = f'bridge vlan del dev {interface} vid 1' - self._cmd(cmd) - vlan_id = interface_config['native_vlan'] - if int(vlan_id) != 1: - if 1 in vlan_add: - vlan_add.remove(1) - vlan_del.add(1) - cmd = f'bridge vlan add dev {interface} vid {vlan_id} pvid untagged master' - self._cmd(cmd) - vlan_add.add(vlan_id) - if vlan_id in vlan_del: - vlan_del.remove(vlan_id) - - if 'allowed_vlan' in interface_config: - vlan_filter = 1 - if 'native_vlan' not in interface_config: - cmd = f'bridge vlan del dev {interface} vid 1' + if int(vlan_filter): + add_vlan = [] + native_vlan_id = None + allowed_vlan_ids= [] + cur_vlan_ids = get_vlan_ids(interface) + + if 'native_vlan' in interface_config: + vlan_id = interface_config['native_vlan'] + add_vlan.append(vlan_id) + native_vlan_id = vlan_id + + if 'allowed_vlan' in interface_config: + for vlan in interface_config['allowed_vlan']: + vlan_range = vlan.split('-') + if len(vlan_range) == 2: + for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1): + add_vlan.append(str(vlan_add)) + allowed_vlan_ids.append(str(vlan_add)) + else: + add_vlan.append(vlan) + allowed_vlan_ids.append(vlan) + + # Remove redundant VLANs from the system + for vlan in list_diff(cur_vlan_ids, add_vlan): + cmd = f'bridge vlan del dev {interface} vid {vlan} master' self._cmd(cmd) - vlan_del.add(1) - for vlan in interface_config['allowed_vlan']: + + for vlan in allowed_vlan_ids: cmd = f'bridge vlan add dev {interface} vid {vlan} master' self._cmd(cmd) - vlan_add.add(vlan) - if vlan in vlan_del: - vlan_del.remove(vlan) - - for vlan in vlan_del: - cmd = f'bridge vlan del dev {ifname} vid {vlan} self' - self._cmd(cmd) - - for vlan in vlan_add: - cmd = f'bridge vlan add dev {ifname} vid {vlan} self' - self._cmd(cmd) - - # enable/disable Vlan Filter - self.set_vlan_filter(vlan_filter) - + # Setting native VLAN to system + if native_vlan_id: + cmd = f'bridge vlan add dev {interface} vid {native_vlan_id} pvid untagged master' + self._cmd(cmd) super().update(config) diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 4014c1a4c..2152dba72 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -63,6 +63,32 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): super().tearDown() + def test_isolated_interfaces(self): + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['stp']) + + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member + ['isolated']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + tmp = get_interface_config(interface) + # STP must be enabled as configured above + self.assertEqual(1, tmp['linkinfo']['info_data']['stp_state']) + + # validate member interface configuration + for member in self._members: + tmp = get_interface_config(member) + # Isolated must be enabled as configured above + self.assertTrue(tmp['linkinfo']['info_slave_data']['isolated']) + + def test_add_remove_bridge_member(self): # Add member interfaces to bridge and set STP cost/priority for interface in self._interfaces: @@ -97,12 +123,34 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): cost += 1 priority += 1 + + def test_vif_8021q_interfaces(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_interfaces() + + def test_vif_8021q_lower_up_down(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_lower_up_down() + + def test_vif_8021q_mtu_limits(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_mtu_limits() + def test_bridge_vlan_filter(self): + vif_vlan = 2 # Add member interface to bridge and set VLAN filter for interface in self._interfaces: base = self._base_path + [interface] - self.cli_set(base + ['vif', '1', 'address', '192.0.2.1/24']) - self.cli_set(base + ['vif', '2', 'address', '192.0.3.1/24']) + self.cli_set(base + ['enable-vlan']) + self.cli_set(base + ['address', '192.0.2.1/24']) + self.cli_set(base + ['vif', str(vif_vlan), 'address', '192.0.3.1/24']) + self.cli_set(base + ['vif', str(vif_vlan), 'mtu', self._mtu]) vlan_id = 101 allowed_vlan = 2 @@ -174,6 +222,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: self.cli_delete(self._base_path + [interface, 'member']) + def test_bridge_vlan_members(self): # T2945: ensure that VIFs are not dropped from bridge vifs = ['300', '400'] diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 5b0046a72..4d3ebc587 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -18,7 +18,6 @@ import os from sys import exit from netifaces import interfaces -import re from vyos.config import Config from vyos.configdict import get_interface_dict @@ -41,26 +40,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def helper_check_removed_vlan(conf,bridge,key,key_mangling): - key_update = re.sub(key_mangling[0], key_mangling[1], key) - if dict_search('member.interface', bridge): - for interface in bridge['member']['interface']: - tmp = leaf_node_changed(conf, ['member', 'interface',interface,key]) - if tmp: - if 'member' in bridge: - if 'interface' in bridge['member']: - if interface in bridge['member']['interface']: - bridge['member']['interface'][interface].update({f'{key_update}_removed': tmp }) - else: - bridge['member']['interface'].update({interface: {f'{key_update}_removed': tmp }}) - else: - bridge['member'].update({ 'interface': {interface: {f'{key_update}_removed': tmp }}}) - else: - bridge.update({'member': { 'interface': {interface: {f'{key_update}_removed': tmp }}}}) - - return bridge - - def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -80,12 +59,6 @@ def get_config(config=None): bridge['member'].update({'interface_remove': tmp }) else: bridge.update({'member': {'interface_remove': tmp }}) - - - # determine which members vlan have been removed - - bridge = helper_check_removed_vlan(conf,bridge,'native-vlan',('-', '_')) - bridge = helper_check_removed_vlan(conf,bridge,'allowed-vlan',('-', '_')) if dict_search('member.interface', bridge): # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: @@ -99,7 +72,6 @@ def get_config(config=None): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - vlan_aware = False for interface,interface_config in bridge['member']['interface'].items(): bridge['member']['interface'][interface] = dict_merge( default_member_values, bridge['member']['interface'][interface]) @@ -120,19 +92,11 @@ def get_config(config=None): # Bridge members must not have an assigned address tmp = has_address_configured(conf, interface) if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) - + # VLAN-aware bridge members must not have VLAN interface configuration - if 'native_vlan' in interface_config: - vlan_aware = True - - if 'allowed_vlan' in interface_config: - vlan_aware = True - - - if vlan_aware: - tmp = has_vlan_subinterface_configured(conf,interface) - if tmp: - if tmp: bridge['member']['interface'][interface].update({'has_vlan' : ''}) + tmp = has_vlan_subinterface_configured(conf,interface) + if 'enable_vlan' in bridge and tmp: + bridge['member']['interface'][interface].update({'has_vlan' : ''}) return bridge @@ -142,8 +106,8 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) - - vlan_aware = False + + ifname = bridge['ifname'] if dict_search('member.interface', bridge): for interface, interface_config in bridge['member']['interface'].items(): @@ -166,31 +130,24 @@ def verify(bridge): if 'has_address' in interface_config: raise ConfigError(error_msg + 'it has an address assigned!') - - if 'has_vlan' in interface_config: - raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!') - - # VLAN-aware bridge members must not have VLAN interface configuration - if 'native_vlan' in interface_config: - vlan_aware = True - - if 'allowed_vlan' in interface_config: - vlan_aware = True - - if vlan_aware and 'wlan' in interface: - raise ConfigError(error_msg + 'VLAN aware cannot be set!') - - if 'allowed_vlan' in interface_config: - for vlan in interface_config['allowed_vlan']: - if re.search('[0-9]{1,4}-[0-9]{1,4}', vlan): - vlan_range = vlan.split('-') - if int(vlan_range[0]) <1 and int(vlan_range[0])>4094: - raise ConfigError('VLAN ID must be between 1 and 4094') - if int(vlan_range[1]) <1 and int(vlan_range[1])>4094: - raise ConfigError('VLAN ID must be between 1 and 4094') - else: - if int(vlan) <1 and int(vlan)>4094: - raise ConfigError('VLAN ID must be between 1 and 4094') + + if 'enable_vlan' in bridge: + if 'has_vlan' in interface_config: + raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!') + + if 'wlan' in interface: + raise ConfigError(error_msg + 'VLAN aware cannot be set!') + else: + for option in ['allowed_vlan', 'native_vlan']: + if option in interface_config: + raise ConfigError('Can not use VLAN options on non VLAN aware bridge') + + if 'enable_vlan' in bridge: + if dict_search('vif.1', bridge): + raise ConfigError(f'VLAN 1 sub interface cannot be set for VLAN aware bridge {ifname}, and VLAN 1 is always the parent interface') + else: + if dict_search('vif', bridge): + raise ConfigError(f'You must first activate "enable-vlan" of {ifname} bridge to use "vif"') return None diff --git a/src/validators/allowed-vlan b/src/validators/allowed-vlan new file mode 100755 index 000000000..11389390b --- /dev/null +++ b/src/validators/allowed-vlan @@ -0,0 +1,19 @@ +#! /usr/bin/python3 + +import sys +import re + +if __name__ == '__main__': + if len(sys.argv)>1: + allowed_vlan = sys.argv[1] + if re.search('[0-9]{1,4}-[0-9]{1,4}', allowed_vlan): + for tmp in allowed_vlan.split('-'): + if int(tmp) not in range(1, 4095): + sys.exit(1) + else: + if int(allowed_vlan) not in range(1, 4095): + sys.exit(1) + else: + sys.exit(2) + + sys.exit(0) -- cgit v1.2.3 From 3a8a99beeb07d1998a348c5dee08a04227d27ac8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 24 Aug 2021 16:52:02 +0200 Subject: vyos.ifconfig: T3772: bugfix missing VRRP interfaces When the interface name was stripped down from "eth0.201" to "eth" to determine the appropriate interface section, VRRP interfaces got left out on the call to rstrip(). VRRP interfaces now show up in "show interfaces" as they did in VyOS 1.2. vyos@vyos:~$ show interfaces Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down Interface IP Address S/L Description --------- ---------- --- ----------- dum0 172.18.254.201/32 u/u eth0 - u/u eth0.10 172.16.33.8/24 u/u eth0.201 172.18.201.10/24 u/u eth1 10.1.1.2/24 u/u eth1v10 10.1.1.1/24 u/u eth2 - u/u lo 127.0.0.1/8 u/u ::1/128 (cherry picked from commit df22bc2c96d5095eaec978a58bf5d2361d758a86) --- python/vyos/ifconfig/section.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 173a90bb4..0e4447b9e 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -46,7 +46,7 @@ class Section: return klass @classmethod - def _basename (cls, name, vlan): + def _basename(cls, name, vlan, vrrp): """ remove the number at the end of interface name name: name of the interface @@ -56,16 +56,18 @@ class Section: name = name.rstrip('.') if vlan: name = name.rstrip('0123456789.') + if vrrp: + name = name.rstrip('0123456789v') return name @classmethod - def section(cls, name, vlan=True): + def section(cls, name, vlan=True, vrrp=True): """ return the name of a section an interface should be under name: name of the interface (eth0, dum1, ...) vlan: should we try try to remove the VLAN from the number """ - name = cls._basename(name, vlan) + name = cls._basename(name, vlan, vrrp) if name in cls._prefixes: return cls._prefixes[name].definition['section'] @@ -79,8 +81,8 @@ class Section: return list(set([cls._prefixes[_].definition['section'] for _ in cls._prefixes])) @classmethod - def klass(cls, name, vlan=True): - name = cls._basename(name, vlan) + def klass(cls, name, vlan=True, vrrp=True): + name = cls._basename(name, vlan, vrrp) if name in cls._prefixes: return cls._prefixes[name] raise ValueError(f'No type found for interface name: {name}') -- cgit v1.2.3 From 4ab2e833c048ae896333d8bfce6a38a42c266c1e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 28 Aug 2021 17:42:33 +0200 Subject: vyos.ifconfig: T3619: only set offloading options if supported by NIC In the past we always told ethtool to change the offloading settings, even if this was not supported by the underlaying driver. This commit will only change the offloading options if they differ from the current state of the NIC and only if it's supported by the NIC. If the NIC does not support setting the offloading options, a message will be displayed for the user: vyos@vyos# set interfaces ethernet eth2 offload gro vyos@vyos# commit [ interfaces ethernet eth2 ] Adapter does not support changing large-receive-offload settings! (cherry picked from commit 31169fa8a763e36f6276632139da46b1aca3a7af) --- python/vyos/ifconfig/ethernet.py | 81 +++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 14 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index df6b96fbf..80044c13e 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -16,6 +16,7 @@ import os import re +from vyos.ethtool import Ethtool from vyos.ifconfig.interface import Interface from vyos.util import run from vyos.util import dict_search @@ -42,7 +43,7 @@ class EthernetIf(Interface): @staticmethod def feature(ifname, option, value): - run(f'ethtool -K {ifname} {option} {value}','ifconfig') + run(f'ethtool -K {ifname} {option} {value}') return False _command_set = {**Interface._command_set, **{ @@ -85,6 +86,10 @@ class EthernetIf(Interface): }, }} + def __init__(self, ifname, **kargs): + super().__init__(ifname, **kargs) + self.ethtool = Ethtool(ifname) + def get_driver_name(self): """ Return the driver name used by NIC. Some NICs don't support all @@ -229,8 +234,16 @@ class EthernetIf(Interface): >>> i.set_gro(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('gro', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_generic_receive_offload() + if not fixed: + enabled = 'on' if enabled else 'off' + if enabled != state: + return self.set_interface('gro', 'on' if state else 'off') + + print('Adapter does not support changing generic-receive-offload settings!') + return False def set_gso(self, state): """ @@ -241,8 +254,16 @@ class EthernetIf(Interface): >>> i.set_gso(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('gso', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_generic_segmentation_offload() + if not fixed: + enabled = 'on' if enabled else 'off' + if enabled != state: + return self.set_interface('gro', 'on' if state else 'off') + + print('Adapter does not support changing generic-segmentation-offload settings!') + return False def set_lro(self, state): """ @@ -253,12 +274,20 @@ class EthernetIf(Interface): >>> i.set_lro(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('lro', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_large_receive_offload() + if not fixed: + enabled = 'on' if enabled else 'off' + if enabled != state: + return self.set_interface('gro', 'on' if state else 'off') + + print('Adapter does not support changing large-receive-offload settings!') + return False def set_rps(self, state): if not isinstance(state, bool): - raise ValueError("Value out of range") + raise ValueError('Value out of range') rps_cpus = '0' if state: @@ -283,8 +312,16 @@ class EthernetIf(Interface): >>> i.set_sg(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('sg', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_scatter_gather() + if not fixed: + enabled = 'on' if enabled else 'off' + if enabled != state: + return self.set_interface('gro', 'on' if state else 'off') + + print('Adapter does not support changing scatter-gather settings!') + return False def set_tso(self, state): """ @@ -296,8 +333,16 @@ class EthernetIf(Interface): >>> i.set_tso(False) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('tso', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_tcp_segmentation_offload() + if not fixed: + enabled = 'on' if enabled else 'off' + if enabled != state: + return self.set_interface('gro', 'on' if state else 'off') + + print('Adapter does not support changing tcp-segmentation-offload settings!') + return False def set_ufo(self, state): """ @@ -309,8 +354,16 @@ class EthernetIf(Interface): >>> i.set_udp_offload(True) """ if not isinstance(state, bool): - raise ValueError("Value out of range") - return self.set_interface('ufo', 'on' if state else 'off') + raise ValueError('Value out of range') + + enabled, fixed = self.ethtool.get_udp_fragmentation_offload() + if not fixed: + enabled = 'on' if enabled else 'off' + if enabled != state: + return self.set_interface('gro', 'on' if state else 'off') + + print('Adapter does not support changing udp-fragmentation-offload settings!') + return False def set_ring_buffer(self, b_type, b_size): """ -- cgit v1.2.3 From 0de23064b9d575ce0569839e3b4453a0c2e9dc1c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Sun, 29 Aug 2021 11:12:01 +0000 Subject: interfaces: T3777: Does not delete empty eui64 address Check eui64_old value before deleting It can be empty or not ipv6 address. --- python/vyos/ifconfig/interface.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index e815c0129..d47403488 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -38,6 +38,7 @@ from vyos.util import dict_search from vyos.util import read_file from vyos.util import get_interface_config 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 @@ -588,9 +589,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): """ -- cgit v1.2.3 From 717894ece10532007ca44118bf63abe200239685 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 30 Aug 2021 21:25:38 +0200 Subject: vyos.ifconfig: T3619: only inform user about real offload change for invalid option Commit 31169fa8 ("vyos.ifconfig: T3619: only set offloading options if supported by NIC") added a warning for the user if an offload option was about to change that was not possible at all (harware limit). Unfortunately the warning was even displayed if nothing was done at all. This got corrected. (cherry picked from commit ce784a9fcb7199f87949f17777b7b736227c85b3) --- python/vyos/ifconfig/ethernet.py | 57 ++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 32 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 80044c13e..cb03a006c 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -237,12 +237,11 @@ class EthernetIf(Interface): raise ValueError('Value out of range') enabled, fixed = self.ethtool.get_generic_receive_offload() - if not fixed: - enabled = 'on' if enabled else 'off' - if enabled != state: + if enabled != state: + if not fixed: return self.set_interface('gro', 'on' if state else 'off') - - print('Adapter does not support changing generic-receive-offload settings!') + else: + print('Adapter does not support changing generic-receive-offload settings!') return False def set_gso(self, state): @@ -257,12 +256,11 @@ class EthernetIf(Interface): raise ValueError('Value out of range') enabled, fixed = self.ethtool.get_generic_segmentation_offload() - if not fixed: - enabled = 'on' if enabled else 'off' - if enabled != state: - return self.set_interface('gro', 'on' if state else 'off') - - print('Adapter does not support changing generic-segmentation-offload settings!') + if enabled != state: + if not fixed: + return self.set_interface('gso', 'on' if state else 'off') + else: + print('Adapter does not support changing generic-segmentation-offload settings!') return False def set_lro(self, state): @@ -277,12 +275,11 @@ class EthernetIf(Interface): raise ValueError('Value out of range') enabled, fixed = self.ethtool.get_large_receive_offload() - if not fixed: - enabled = 'on' if enabled else 'off' - if enabled != state: + if enabled != state: + if not fixed: return self.set_interface('gro', 'on' if state else 'off') - - print('Adapter does not support changing large-receive-offload settings!') + else: + print('Adapter does not support changing large-receive-offload settings!') return False def set_rps(self, state): @@ -315,12 +312,11 @@ class EthernetIf(Interface): raise ValueError('Value out of range') enabled, fixed = self.ethtool.get_scatter_gather() - if not fixed: - enabled = 'on' if enabled else 'off' - if enabled != state: + if enabled != state: + if not fixed: return self.set_interface('gro', 'on' if state else 'off') - - print('Adapter does not support changing scatter-gather settings!') + else: + print('Adapter does not support changing scatter-gather settings!') return False def set_tso(self, state): @@ -336,12 +332,11 @@ class EthernetIf(Interface): raise ValueError('Value out of range') enabled, fixed = self.ethtool.get_tcp_segmentation_offload() - if not fixed: - enabled = 'on' if enabled else 'off' - if enabled != state: + if enabled != state: + if not fixed: return self.set_interface('gro', 'on' if state else 'off') - - print('Adapter does not support changing tcp-segmentation-offload settings!') + else: + print('Adapter does not support changing tcp-segmentation-offload settings!') return False def set_ufo(self, state): @@ -357,12 +352,11 @@ class EthernetIf(Interface): raise ValueError('Value out of range') enabled, fixed = self.ethtool.get_udp_fragmentation_offload() - if not fixed: - enabled = 'on' if enabled else 'off' - if enabled != state: + if enabled != state: + if not fixed: return self.set_interface('gro', 'on' if state else 'off') - - print('Adapter does not support changing udp-fragmentation-offload settings!') + else: + print('Adapter does not support changing udp-fragmentation-offload settings!') return False def set_ring_buffer(self, b_type, b_size): @@ -382,7 +376,6 @@ class EthernetIf(Interface): print(f'could not set "{b_type}" ring-buffer for {ifname}') return output - 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 -- cgit v1.2.3 From f5e46ee6cc2b6c1c1869e26beca4ccd5bf52b62f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 30 Aug 2021 21:36:52 +0200 Subject: ethernet: T3787: remove deprecated UDP fragmentation offloading option Deprecated in the Linux Kernel by commit 08a00fea6de277df12ccfadc21 ("net: Remove references to NETIF_F_UFO from ethtool."). --- interface-definitions/interfaces-ethernet.xml.in | 6 ----- python/vyos/ethtool.py | 3 --- python/vyos/ifconfig/ethernet.py | 28 ------------------------ src/migration-scripts/interfaces/20-to-21 | 12 ++++------ 4 files changed, 4 insertions(+), 45 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index ec20bca8d..27d555552 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -101,12 +101,6 @@ - - - Enable UDP Fragmentation Offloading - - - diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index a81ddac31..397be6bb2 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -122,9 +122,6 @@ class Ethtool: def get_tcp_segmentation_offload(self): return self._get_generic('tcp-segmentation-offload') - def get_udp_fragmentation_offload(self): - return self._get_generic('udp-fragmentation-offload') - def get_rx_buffer(self): # Configuration of RX ring-buffers is not supported on every device, # thus when it's impossible return None diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index cb03a006c..a6c7f5f25 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -72,11 +72,6 @@ class EthernetIf(Interface): 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v), # 'shellcmd': 'ethtool -K {ifname} tso {value}', }, - 'ufo': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'ufo', v), - # 'shellcmd': 'ethtool -K {ifname} ufo {value}', - }, }} _sysfs_set = {**Interface._sysfs_set, **{ @@ -339,26 +334,6 @@ class EthernetIf(Interface): print('Adapter does not support changing tcp-segmentation-offload settings!') return False - def set_ufo(self, state): - """ - Enable UDP fragmentation offloading. State can be either True or False. - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_udp_offload(True) - """ - if not isinstance(state, bool): - raise ValueError('Value out of range') - - enabled, fixed = self.ethtool.get_udp_fragmentation_offload() - if enabled != state: - if not fixed: - return self.set_interface('gro', 'on' if state else 'off') - else: - print('Adapter does not support changing udp-fragmentation-offload settings!') - return False - def set_ring_buffer(self, b_type, b_size): """ Example: @@ -404,9 +379,6 @@ class EthernetIf(Interface): # TSO (TCP segmentation offloading) self.set_tso(dict_search('offload.tso', config) != None) - # UDP fragmentation offloading - self.set_ufo(dict_search('offload.ufo', config) != None) - # Set physical interface speed and duplex if {'speed', 'duplex'} <= set(config): speed = config.get('speed') diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index 9210330d6..4b0e70d35 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -15,7 +15,8 @@ # along with this program. If not, see . # T3619: mirror Linux Kernel defaults for ethernet offloading options into VyOS -# CLI. See https://phabricator.vyos.net/T3619#102254 for all the details. +# CLI. See https://phabricator.vyos.net/T3619#102254 for all the details. +# T3787: Remove deprecated UDP fragmentation offloading option from sys import argv @@ -84,14 +85,9 @@ for ifname in config.list_nodes(base): elif enabled and not fixed: config.set(base + [ifname, 'offload', 'tso']) - # If UFO is enabled by the Kernel - we reflect this on the CLI. If UFO is - # enabled via CLI but not supported by the NIC - we remove it from the CLI - configured = config.exists(base + [ifname, 'offload', 'ufo']) - enabled, fixed = eth.get_udp_fragmentation_offload() - if configured and fixed: + # Remove deprecated UDP fragmentation offloading option + if config.exists(base + [ifname, 'offload', 'ufo']): config.delete(base + [ifname, 'offload', 'ufo']) - elif enabled and not fixed: - config.set(base + [ifname, 'offload', 'ufo']) try: with open(file_name, 'w') as f: -- cgit v1.2.3 From 4281fb32ebba0f1e0c76bf1f21eea7defe65a1a9 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 31 Aug 2021 08:32:49 +0000 Subject: interface: T3782: Fix unexpected delete qdisc rule Some tc qdisc rules are generated by old perl code It prevent to unexpected override this code by python. --- python/vyos/ifconfig/interface.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d47403488..c53bb964a 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1079,12 +1079,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: -- cgit v1.2.3 From 862e6e96bfc557974dbbe374d0aefe654b76e664 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 12:22:36 +0200 Subject: vyos.ethtool: T3163: use long option names when calling the ethtool binray This makes understanding the code easier what is "really" called without opening the man page. (cherry picked from commit a086dc2c429aea9614ac7a9c735c6475c2d6da59) --- op-mode-definitions/show-interfaces-ethernet.xml.in | 10 +++++----- python/vyos/ethtool.py | 2 +- python/vyos/ifconfig/ethernet.py | 7 +------ 3 files changed, 7 insertions(+), 12 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/op-mode-definitions/show-interfaces-ethernet.xml.in b/op-mode-definitions/show-interfaces-ethernet.xml.in index bdcfa55f1..897412295 100644 --- a/op-mode-definitions/show-interfaces-ethernet.xml.in +++ b/op-mode-definitions/show-interfaces-ethernet.xml.in @@ -23,19 +23,19 @@ Visually identify specified ethernet interface - echo "Blinking interface $4 for 30 seconds."; /sbin/ethtool --identify "$4" 30 + echo "Blinking interface $4 for 30 seconds."; ethtool --identify "$4" 30 Show physical device information for specified ethernet interface - /sbin/ethtool "$4"; /sbin/ethtool -i "$4" + ethtool "$4"; ethtool --driver "$4" Show physical device offloading capabilities - /sbin/ethtool -k "$4" | sed -e 1d -e '/fixed/d' -e 's/^\t*//g' -e 's/://' | column -t -s' ' + ethtool --show-features "$4" | sed -e 1d -e '/fixed/d' -e 's/^\t*//g' -e 's/://' | column -t -s' ' @@ -43,13 +43,13 @@ Show physical device statistics for specified ethernet interface - /sbin/ethtool -S "$4" + ethtool --statistics "$4" Show transceiver information from modules (e.g SFP+, QSFP) - /sbin/ethtool -m "$4" + ethtool --module-info "$4" diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 397be6bb2..55b7b776f 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -65,7 +65,7 @@ class Ethtool: self._speed_duplex[speed].update({ duplex : ''}) # Now populate features dictionaty - out, err = popen(f'ethtool -k {ifname}') + out, err = popen(f'ethtool --show-features {ifname}') # skip the first line, it only says: "Features for eth0": for line in out.splitlines()[1:]: if ":" in line: diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index a6c7f5f25..2c9d99b91 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -43,34 +43,29 @@ class EthernetIf(Interface): @staticmethod def feature(ifname, option, value): - run(f'ethtool -K {ifname} {option} {value}') + run(f'ethtool --features {ifname} {option} {value}') return False _command_set = {**Interface._command_set, **{ 'gro': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v), - # 'shellcmd': 'ethtool -K {ifname} gro {value}', }, 'gso': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'gso', v), - # 'shellcmd': 'ethtool -K {ifname} gso {value}', }, 'lro': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), - # 'shellcmd': 'ethtool -K {ifname} lro {value}', }, 'sg': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v), - # 'shellcmd': 'ethtool -K {ifname} sg {value}', }, 'tso': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v), - # 'shellcmd': 'ethtool -K {ifname} tso {value}', }, }} -- cgit v1.2.3 From 5cbb1f3e4adba39d790f378afabb1e45416aff7c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 21:28:08 +0200 Subject: vyos.ethtool: T3163: purify code to read current speed and duplex settings It makes no sense to have a parser for the ethtool value sin ethtool.py and ethernet.py - one instance ios more then enough! (cherry picked from commit 6f5fb5c503b5df96d0686002355da3633b1fc597) --- python/vyos/ethtool.py | 14 +++++++++++++- python/vyos/ifconfig/ethernet.py | 22 +++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index fb2e49c1d..e803e28a1 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -44,6 +44,7 @@ class Ethtool: _speed_duplex = { } _ring_buffers = { } _driver_name = None + _auto_negotiation = None def __init__(self, ifname): # Get driver used for interface @@ -65,7 +66,6 @@ class Ethtool: reading = True if 'Supported pause frame use:' in line: reading = False - break if reading: for block in line.split(): if pattern.search(block): @@ -75,6 +75,15 @@ class Ethtool: self._speed_duplex.update({ speed : {}}) if duplex not in self._speed_duplex[speed]: self._speed_duplex[speed].update({ duplex : ''}) + if 'Auto-negotiation:' in line: + # Split the following string: Auto-negotiation: off + # we are only interested in off or on + tmp = line.split()[-1] + self._auto_negotiation = bool(tmp == 'on') + + if self._auto_negotiation == None: + raise ValueError(f'Could not determine auto-negotiation settings '\ + f'for interface {ifname}!') # Now populate features dictionaty out, err = popen(f'ethtool --show-features {ifname}') @@ -161,3 +170,6 @@ class Ethtool: if duplex in self._speed_duplex[speed]: return True return False + + def get_auto_negotiation(self): + return self._auto_negotiation diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 2c9d99b91..d6e42db99 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -20,6 +20,7 @@ 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 @Interface.register @@ -182,32 +183,19 @@ class EthernetIf(Interface): # Get current speed and duplex settings: ifname = self.config['ifname'] - cmd = f'ethtool {ifname}' - tmp = self._cmd(cmd) - - if re.search("\tAuto-negotiation: on", tmp): + if self.ethtool.get_auto_negotiation(): if speed == 'auto' and duplex == 'auto': # bail out early as nothing is to change return else: # read in current speed and duplex settings - cur_speed = 0 - cur_duplex = '' - for line in tmp.splitlines(): - if line.lstrip().startswith("Speed:"): - non_decimal = re.compile(r'[^\d.]+') - cur_speed = non_decimal.sub('', line) - continue - - if line.lstrip().startswith("Duplex:"): - cur_duplex = line.split()[-1].lower() - break - + cur_speed = read_file(f'/sys/class/net/{ifname}/speed') + cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex') if (cur_speed == speed) and (cur_duplex == duplex): # bail out early as nothing is to change return - cmd = f'ethtool -s {ifname}' + cmd = f'ethtool --change {ifname}' if speed == 'auto' or duplex == 'auto': cmd += ' autoneg on' else: -- cgit v1.2.3 From de3c476ccb60d66a844f9e12a9ca2963ae2206e6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 21:50:05 +0200 Subject: ethernet: T3163: only change ring-buffer settings if required Only update the RX/TX ring-buffer settings if they are different from the ones currently programmed to the hardware. There is no need to write the same value to the hardware again - this could cause traffic disruption on some NICs. (cherry picked from commit 29082959e0efc02462fba8560d6726096e8743e9) --- python/vyos/ethtool.py | 29 ++++++++++++++++++++++------- python/vyos/ifconfig/ethernet.py | 15 ++++++++++----- src/conf_mode/interfaces-ethernet.py | 4 ++-- 3 files changed, 34 insertions(+), 14 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index e803e28a1..f5796358d 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -43,6 +43,7 @@ class Ethtool: # } _speed_duplex = { } _ring_buffers = { } + _ring_buffers_max = { } _driver_name = None _auto_negotiation = None @@ -99,10 +100,20 @@ class Ethtool: 'fixed' : fixed } - out, err = popen(f'ethtool -g {ifname}') + out, err = popen(f'ethtool --show-ring {ifname}') # We are only interested in line 2-5 which contains the device maximum # ringbuffers for line in out.splitlines()[2:6]: + if ':' in line: + key, value = [s.strip() for s in line.strip().split(":", 1)] + key = key.lower().replace(' ', '_') + # T3645: ethtool version used on Debian Bullseye changed the + # output format from 0 -> n/a. As we are only interested in the + # tx/rx keys we do not care about RX Mini/Jumbo. + if value.isdigit(): + self._ring_buffers_max[key] = int(value) + # Now we wan't to get the current RX/TX ringbuffer values - used for + for line in out.splitlines()[7:11]: if ':' in line: key, value = [s.strip() for s in line.strip().split(":", 1)] key = key.lower().replace(' ', '_') @@ -143,15 +154,19 @@ class Ethtool: def get_tcp_segmentation_offload(self): return self._get_generic('tcp-segmentation-offload') - def get_rx_buffer(self): - # Configuration of RX ring-buffers is not supported on every device, + def get_ring_buffer_max(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None - return self._ring_buffers.get('rx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return self._ring_buffers_max.get(rx_tx, None) - def get_tx_buffer(self): - # Configuration of TX ring-buffers is not supported on every device, + def get_ring_buffer(self, rx_tx): + # Configuration of RX/TX ring-buffers is not supported on every device, # thus when it's impossible return None - return self._ring_buffers.get('tx', None) + if rx_tx not in ['rx', 'tx']: + ValueError('Ring-buffer type must be either "rx" or "tx"') + return self._ring_buffers.get(rx_tx, None) def check_speed_duplex(self, speed, duplex): """ Check if the passed speed and duplex combination is supported by diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index d6e42db99..5974a3d8f 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -317,21 +317,26 @@ class EthernetIf(Interface): print('Adapter does not support changing tcp-segmentation-offload settings!') return False - def set_ring_buffer(self, b_type, b_size): + def set_ring_buffer(self, rx_tx, size): """ Example: >>> from vyos.ifconfig import EthernetIf >>> i = EthernetIf('eth0') >>> i.set_ring_buffer('rx', '4096') """ + current_size = self.ethtool.get_ring_buffer(rx_tx) + if current_size == size: + # bail out early if nothing is about to change + return None + ifname = self.config['ifname'] - cmd = f'ethtool -G {ifname} {b_type} {b_size}' + cmd = f'ethtool --set-ring {ifname} {rx_tx} {size}' output, code = self._popen(cmd) # ethtool error codes: # 80 - value already setted # 81 - does not possible to set value if code and code != 80: - print(f'could not set "{b_type}" ring-buffer for {ifname}') + print(f'could not set "{rx_tx}" ring-buffer for {ifname}') return output def update(self, config): @@ -370,8 +375,8 @@ class EthernetIf(Interface): # Set interface ring buffer if 'ring_buffer' in config: - for b_type in config['ring_buffer']: - self.set_ring_buffer(b_type, config['ring_buffer'][b_type]) + for rx_tx, size in config['ring_buffer'].items(): + self.set_ring_buffer(rx_tx, size) # call base class first super().update(config) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 57e05d4ea..f3f3fede8 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -79,11 +79,11 @@ def verify(ethernet): f'settings to: {speed}/{duplex}!') if 'ring_buffer' in ethernet: - max_rx = ethtool.get_rx_buffer() + max_rx = ethtool.get_ring_buffer_max('rx') if not max_rx: raise ConfigError('Driver does not support RX ring-buffer configuration!') - max_tx = ethtool.get_tx_buffer() + max_tx = ethtool.get_ring_buffer_max('tx') if not max_tx: raise ConfigError('Driver does not support TX ring-buffer configuration!') -- cgit v1.2.3 From b0d4112bd6073e4a947869c3bd80f8e87783fbfa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 31 Aug 2021 23:03:01 +0200 Subject: vyos.ethtool: T3163: purify code to read and change flow-control settings It makes no sense to have a parser for the ethtool values in ethtool.py and ethernet.py - one instance ios more then enough! (cherry picked from commit 0229645c8248decb5664056df8aa5cd5dff41802) --- python/vyos/ethtool.py | 23 +++++++++++++++++ python/vyos/ifconfig/ethernet.py | 42 ++++++++----------------------- src/conf_mode/interfaces-ethernet.py | 4 +++ src/migration-scripts/interfaces/20-to-21 | 8 ++++++ 4 files changed, 45 insertions(+), 32 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index f5796358d..87b9d7dd0 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -46,6 +46,8 @@ class Ethtool: _ring_buffers_max = { } _driver_name = None _auto_negotiation = None + _flow_control = None + _flow_control_enabled = None def __init__(self, ifname): # Get driver used for interface @@ -123,6 +125,15 @@ class Ethtool: if value.isdigit(): self._ring_buffers[key] = int(value) + # Get current flow control settings, but this is not supported by + # all NICs (e.g. vmxnet3 does not support is) + out, err = popen(f'ethtool --show-pause {ifname}') + if len(out.splitlines()) > 1: + self._flow_control = True + # read current flow control setting, this returns: + # ['Autonegotiate:', 'on'] + self._flow_control_enabled = out.splitlines()[1].split()[-1] + def _get_generic(self, feature): """ Generic method to read self._features and return a tuple for feature @@ -186,5 +197,17 @@ class Ethtool: return True return False + def check_flow_control(self): + """ Check if the NIC supports flow-control """ + if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + return False + return self._flow_control + + def get_flow_control(self): + if self._flow_control_enabled == None: + raise ValueError('Interface does not support changing '\ + 'flow-control settings!') + return self._flow_control_enabled + def get_auto_negotiation(self): return self._auto_negotiation diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 5974a3d8f..cb07693c3 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -122,38 +122,16 @@ class EthernetIf(Interface): 'flow control settings!') return - # Get current flow control settings: - cmd = f'ethtool --show-pause {ifname}' - output, code = self._popen(cmd) - if code == 76: - # the interface does not support it - return '' - if code: - # never fail here as it prevent vyos to boot - print(f'unexpected return code {code} from {cmd}') - return '' - - # The above command returns - with tabs: - # - # Pause parameters for eth0: - # Autonegotiate: on - # RX: off - # TX: off - if re.search("Autonegotiate:\ton", output): - if enable == "on": - # flowcontrol is already enabled - no need to re-enable it again - # this will prevent the interface from flapping as applying the - # flow-control settings will take the interface down and bring - # it back up every time. - return '' - - # Assemble command executed on system. Unfortunately there is no way - # to change this setting via sysfs - 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}') - return output + current = self.ethtool.get_flow_control() + if current != enable: + # Assemble command executed on system. Unfortunately there is no way + # to change this setting via sysfs + 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}') + return output + return None def set_speed_duplex(self, speed, duplex): """ diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index f3f3fede8..6e0d8c4e8 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -78,6 +78,10 @@ def verify(ethernet): raise ConfigError(f'Adapter does not support changing speed and duplex '\ f'settings to: {speed}/{duplex}!') + if 'disable_flow_control' in ethernet: + if not ethtool.check_flow_control(): + raise ConfigError('Adapter does not support changing flow-control settings!') + if 'ring_buffer' in ethernet: max_rx = ethtool.get_ring_buffer_max('rx') if not max_rx: diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index bd89dcdb4..0bd858760 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -104,6 +104,14 @@ for ifname in config.list_nodes(base): config.delete(speed_path) config.delete(duplex_path) + # Also while processing the interface configuration, not all adapters support + # changing disabling flow-control - or change this setting. If disabling + # flow-control is not supported by the NIC, we remove the setting from CLI + flow_control_path = base + [ifname, 'disable-flow-control'] + if config.exists(flow_control_path): + if not eth.check_flow_control(): + config.delete(flow_control_path) + try: with open(file_name, 'w') as f: f.write(config.to_string()) -- cgit v1.2.3 From 1572edd2cef355710d1129907d3e49451a6c31d4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Sep 2021 16:45:30 +0200 Subject: ethernet: T3802: check if driver supports changing flow-control settings --- python/vyos/ethtool.py | 2 +- python/vyos/ifconfig/ethernet.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 609d83b5e..7e46969cf 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -46,7 +46,7 @@ class Ethtool: _ring_buffers_max = { } _driver_name = None _auto_negotiation = None - _flow_control = None + _flow_control = False _flow_control_enabled = None def __init__(self, ifname): diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index cb07693c3..47d3b6b4d 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -116,11 +116,10 @@ class EthernetIf(Interface): if enable not in ['on', 'off']: raise ValueError("Value out of range") - driver_name = self.get_driver_name() - if driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if not self.ethtool.check_flow_control(): self._debug_msg(f'{driver_name} driver does not support changing '\ 'flow control settings!') - return + return False current = self.ethtool.get_flow_control() if current != enable: -- cgit v1.2.3 From 07840977834816b69fa3b366817d90f44b5dc7a7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Sep 2021 16:46:55 +0200 Subject: ethernet: T3802: use only one implementation for get_driver_name() Move the two implementations to get the driver name of a NIC from ethernet.py and ethtool.py to only ethtool.py. --- python/vyos/ethtool.py | 13 ++++++++----- python/vyos/ifconfig/ethernet.py | 28 +++------------------------- src/conf_mode/interfaces-ethernet.py | 2 +- 3 files changed, 12 insertions(+), 31 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 7e46969cf..4efc3a234 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -134,6 +134,12 @@ class Ethtool: # ['Autonegotiate:', 'on'] self._flow_control_enabled = out.splitlines()[1].split()[-1] + def get_auto_negotiation(self): + return self._auto_negotiation + + def get_driver_name(self): + return self._driver_name + def _get_generic(self, feature): """ Generic method to read self._features and return a tuple for feature @@ -189,7 +195,7 @@ class Ethtool: if duplex not in ['full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') - if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: return False if speed in self._speed_duplex: @@ -199,7 +205,7 @@ class Ethtool: def check_flow_control(self): """ Check if the NIC supports flow-control """ - if self._driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: return False return self._flow_control @@ -208,6 +214,3 @@ class Ethtool: raise ValueError('Interface does not support changing '\ 'flow-control settings!') return self._flow_control_enabled - - def get_auto_negotiation(self): - return self._auto_negotiation diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 47d3b6b4d..50e865203 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -81,25 +81,6 @@ class EthernetIf(Interface): super().__init__(ifname, **kargs) self.ethtool = Ethtool(ifname) - def get_driver_name(self): - """ - Return the driver name used by NIC. Some NICs don't support all - features e.g. changing link-speed, duplex - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.get_driver_name() - 'vmxnet3' - """ - ifname = self.config['ifname'] - sysfs_file = f'/sys/class/net/{ifname}/device/driver/module' - if os.path.exists(sysfs_file): - link = os.readlink(sysfs_file) - return os.path.basename(link) - else: - return None - def set_flow_control(self, enable): """ Changes the pause parameters of the specified Ethernet device. @@ -117,8 +98,7 @@ class EthernetIf(Interface): raise ValueError("Value out of range") if not self.ethtool.check_flow_control(): - self._debug_msg(f'{driver_name} driver does not support changing '\ - 'flow control settings!') + self._debug_msg(f'NIC driver does not support changing flow control settings!') return False current = self.ethtool.get_flow_control() @@ -152,10 +132,8 @@ class EthernetIf(Interface): if duplex not in ['auto', 'full', 'half']: raise ValueError("Value out of range (duplex)") - driver_name = self.get_driver_name() - if driver_name in ['vmxnet3', 'virtio_net', 'xen_netfront']: - self._debug_msg(f'{driver_name} driver does not support changing '\ - 'speed/duplex settings!') + if not self.ethtool.check_speed_duplex(speed, duplex): + self._debug_msg(f'NIC driver does not support changing speed/duplex settings!') return # Get current speed and duplex settings: diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 17f58b285..de851262b 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -113,7 +113,7 @@ def verify(ethernet): if not os.path.exists(f'/sys/class/net/{ifname}/queues/rx-0/rps_cpus'): raise ConfigError('Interface does not suport RPS!') - driver = EthernetIf(ifname).get_driver_name() + driver = ethtool.get_driver_name() # T3342 - Xen driver requires special treatment if driver == 'vif': if int(ethernet['mtu']) > 1500 and dict_search('offload.sg', ethernet) == None: -- cgit v1.2.3 From e2b7e1766cc22c5cd718a5001be6336bdca92eec Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Sep 2021 16:47:42 +0200 Subject: ethernet: T3802: not all NICs support reading speed/duplex settings in all states Turns out an AX88179 USB 3.0 NIC does not support reading back the speed and duplex settings in every operating state. While the NIC is beeing initialized, reading the speed setting will return: $ cat /sys/class/net/eth6/speed cat: /sys/class/net/eth6/speed: Invalid argument Thus if this happens, we simply tell the system that the current NIC speed matches the requested speed and nothing is changed at this point in time. --- python/vyos/ifconfig/ethernet.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 50e865203..d06b0a842 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -143,9 +143,12 @@ class EthernetIf(Interface): # bail out early as nothing is to change return else: - # read in current speed and duplex settings - cur_speed = read_file(f'/sys/class/net/{ifname}/speed') - cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex') + # XXX: read in current speed and duplex settings + # There are some "nice" NICs like AX88179 which do not support + # reading the speed thus we simply fallback to the supplied speed + # to not cause any change here and raise an exception. + cur_speed = read_file(f'/sys/class/net/{ifname}/speed', speed) + cur_duplex = read_file(f'/sys/class/net/{ifname}/duplex', duplex) if (cur_speed == speed) and (cur_duplex == duplex): # bail out early as nothing is to change return -- cgit v1.2.3 From 3efe74df68ea2d797155a1371cb0b321f5437f25 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Sep 2021 21:41:56 +0200 Subject: vyos.ifconfig: T2738: add delta check when changing interface parameters There is no need to alter interface parameters if they have not changed at all. (cherry picked from commit b4c58c5aefaca4fce817b58327b9c7c3e8145d6d) --- python/vyos/ifconfig/interface.py | 122 +++++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 9 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index c53bb964a..9a3419353 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -37,6 +37,7 @@ 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 @@ -108,6 +109,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 = { @@ -139,7 +144,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': { @@ -209,6 +213,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}') @@ -359,6 +408,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): @@ -421,7 +473,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. @@ -430,6 +482,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) def set_arp_cache_tmo(self, tmo): @@ -441,6 +498,10 @@ 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_arp_filter(self, arp_filter): @@ -461,6 +522,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): @@ -477,6 +541,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): @@ -498,6 +565,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): @@ -510,12 +580,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): @@ -544,6 +618,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): @@ -559,6 +636,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): @@ -566,6 +646,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): @@ -619,6 +702,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): @@ -626,6 +712,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): @@ -648,6 +737,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): @@ -672,6 +764,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): @@ -747,6 +842,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): @@ -773,6 +871,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): @@ -1015,7 +1116,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: @@ -1061,8 +1164,9 @@ class Interface(Control): # for interfaces which are yet not up and running. return self._popen(f'systemctl restart dhcp6c@{ifname}.service') else: - self._popen(f'systemctl stop dhcp6c@{ifname}.service') - + systemd_service = f'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) @@ -1183,7 +1287,7 @@ class Interface(Control): # unbinding will call 'ip link set dev eth0 nomaster' which will # also drop the interface out of a bridge or bond - thus this is # checked before - self.set_vrf(config.get('vrf', '')) + self.set_vrf(config.get('vrf', None)) # Configure ARP cache timeout in milliseconds - has default value tmp = dict_search('ip.arp_cache_timeout', config) -- cgit v1.2.3 From e8c6595fc477573887efcdb55ba6a286587b214b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Sep 2021 22:06:02 +0200 Subject: vyos.ifconfig: T2738: do not remove OS assigned IP addresses from interface When using VRRP on any given interface and performing an action against that interface - be it even only changing the alias - will trigger a removal of the VRRP IP address. The issue is caused by: # 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): When the script calls into the library - we will drop all IP addresses set on the adapter but not available in the config dict. We should only remove the IP addresses marked by the CLI to be deleted! (cherry picked from commit e80d0aebd691f1a707ab534b4d1340fa0b793e01) --- python/vyos/configdict.py | 3 +++ python/vyos/ifconfig/interface.py | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 06e5faf46..73986e9af 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -375,6 +375,9 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2665: blend in proper DHCPv6-PD default values dict = T2665_set_dhcpv6pd_defaults(dict) + address = leaf_node_changed(config, ['address']) + if address: dict.update({'address_old' : address}) + # Check if we are a member of a bridge device bridge = is_member(config, ifname, 'bridge') if bridge: dict.update({'is_bridge_member' : bridge}) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 9a3419353..2629729f8 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1010,6 +1010,8 @@ class Interface(Control): >>> j.get_addr() ['2001:db8::ffff/64'] """ + if not addr: + raise ValueError() # remove from interface if addr == 'dhcp': @@ -1261,16 +1263,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) -- cgit v1.2.3 From a78183e1854ba1588fc6f3ee6ba83f4f3805865e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 20 Sep 2021 07:38:59 +0200 Subject: vyos.ifconfig: T2738: can only read from a file when it exists When IPv6 is disbaled on an interface also the sysfs files related to IPv6 for this interface vanish. We need to check if the file exists before we read it. (cherry picked from commit 672a70613aa6c987bca417f93b587eddccbfd53a) --- python/vyos/ifconfig/control.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index 43136f361..6815074f8 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -18,11 +18,12 @@ import os from inspect import signature from inspect import _empty -from vyos import debug +from vyos.ifconfig.section import Section from vyos.util import popen from vyos.util import cmd -from vyos.ifconfig.section import Section - +from vyos.util import read_file +from vyos.util import write_file +from vyos import debug class Control(Section): _command_get = {} @@ -116,20 +117,18 @@ class Control(Section): Provide a single primitive w/ error checking for reading from sysfs. """ value = None - with open(filename, 'r') as f: - value = f.read().rstrip('\n') - - self._debug_msg("read '{}' < '{}'".format(value, filename)) + if os.path.exists(filename): + value = read_file(filename) + self._debug_msg("read '{}' < '{}'".format(value, filename)) return value def _write_sysfs(self, filename, value): """ Provide a single primitive w/ error checking for writing to sysfs. """ - self._debug_msg("write '{}' > '{}'".format(value, filename)) if os.path.isfile(filename): - with open(filename, 'w') as f: - f.write(str(value)) + write_file(filename, str(value)) + self._debug_msg("write '{}' > '{}'".format(value, filename)) return True return False -- cgit v1.2.3 From c330504ceda582daca8c4982e0cf8adfb556f15d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 20 Sep 2021 21:50:56 +0200 Subject: vyos.ifconfig: get_mac_synthetic() must generate a stable "MAC" Commit b7d30137b1 ("vyos.ifconfig: provide generic get_mac_synthetic() method") provided a common helper to generate MAC addresses used by EUI64 addresses for interfaces not having a layer2 interface (WireGuard or ip tunnel). The problem is that every call to the helper always yielded a new MAC address. This becomes problematic when IPv6 link-local addresses are generated and modified on the interface as multiple link-local (fe80::/64) addresses can easily be added to the interface leaving ... a mess. This commit changes the way how the "synthetic" MAC is generated, we generate a UUID which is stable as it is based on the interface name. We take out the last 48 bits of the UUID and form the "MAC" address. (cherry picked from commit 081e23996feb60ad903caf8b0a4587f5dacc69bf) --- python/vyos/ifconfig/interface.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 2629729f8..de46d3d66 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -27,6 +27,8 @@ from netifaces import ifaddresses # this is not the same as socket.AF_INET/INET6 from netifaces import AF_INET from netifaces import AF_INET6 +from uuid import uuid3 +from uuid import NAMESPACE_DNS from vyos import ConfigError from vyos.configdict import list_diff @@ -56,7 +58,6 @@ from vyos.ifconfig import Section from netaddr import EUI from netaddr import mac_unix_expanded -from random import getrandbits class Interface(Control): # This is the class which will be used to create @@ -438,9 +439,14 @@ class Interface(Control): >>> Interface('eth0').get_mac() '00:50:ab:cd:ef:00' """ - # we choose 40 random bytes for the MAC address, this gives - # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') - tmp = EUI(getrandbits(48)).value + # calculate a UUID based on the interface name - this is as predictable + # as an interface MAC address and thus can be used in the same way + tmp = uuid3(NAMESPACE_DNS, self.ifname) + # take the last 48 bits from the UUID string + tmp = str(tmp).split('-')[-1] + # 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 -- cgit v1.2.3 From a6c5874445eba0eae5cb89a95358f4c6c74b7f79 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 20 Sep 2021 21:55:43 +0200 Subject: ifconfig: T2104: cleanup IPv6 EUI-64 handling in update() (cherry picked from commit 3f6ae12908f54222f2f79a87bed51f71e2fbac87) --- python/vyos/ifconfig/interface.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index de46d3d66..89a562cf6 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1369,16 +1369,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 () - 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') -- cgit v1.2.3 From 65398e5c8aedf2f206bb706e97aa828e409d07b3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 20:29:36 +0200 Subject: vrrp: keepalived: T616: move configuration to volatile /run directory Move keepalived configuration from /etc/keepalived to /run/keepalived. (cherry picked from commit b243795eba1b36cadd81c3149e833bdf5c5bea70) --- data/templates/vrrp/keepalived.conf.tmpl | 3 +-- python/vyos/ifconfig/vrrp.py | 8 ++++---- smoketest/scripts/cli/test_ha_vrrp.py | 6 ++---- src/conf_mode/vrrp.py | 5 ++++- src/etc/systemd/system/keepalived.service.d/override.conf | 10 ++++++++++ src/system/keepalived-fifo.py | 14 +++++++------- 6 files changed, 28 insertions(+), 18 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index 13619ca69..c9835049a 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -5,7 +5,7 @@ global_defs { dynamic_interfaces script_user root - notify_fifo /run/keepalived_notify_fifo + notify_fifo /run/keepalived/keepalived_notify_fifo notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py } @@ -16,7 +16,6 @@ vrrp_script healthcheck_{{ group.name }} { interval {{ group.health_check_interval }} fall {{ group.health_check_count }} rise 1 - } {% endif %} diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index b522cc1ab..481b0284a 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -32,14 +32,14 @@ class VRRPNoData(VRRPError): class VRRP(object): _vrrp_prefix = '00:00:5E:00:01:' location = { - 'pid': '/run/keepalived.pid', - 'fifo': '/run/keepalived_notify_fifo', + 'pid': '/run/keepalived/keepalived.pid', + 'fifo': '/run/keepalived/keepalived_notify_fifo', 'state': '/tmp/keepalived.data', 'stats': '/tmp/keepalived.stats', 'json': '/tmp/keepalived.json', 'daemon': '/etc/default/keepalived', - 'config': '/etc/keepalived/keepalived.conf', - 'vyos': '/run/keepalived_config.dict', + 'config': '/run/keepalived/keepalived.conf', + 'vyos': '/run/keepalived/keepalived_config.dict', } _signal = { diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index 03618c7d8..9c8d26699 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -14,22 +14,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import re import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file - from vyos.template import inc_ip PROCESS_NAME = 'keepalived' -KEEPALIVED_CONF = '/etc/keepalived/keepalived.conf' +KEEPALIVED_CONF = VRRP.location['config'] base_path = ['high-availability', 'vrrp'] vrrp_interface = 'eth1' diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 4cee87003..cee6a9ba2 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -30,6 +30,7 @@ import vyos.config from vyos import ConfigError from vyos.util import call +from vyos.util import makedir from vyos.template import render from vyos.ifconfig.vrrp import VRRP @@ -136,7 +137,9 @@ def get_config(config=None): sync_groups.append(sync_group) # create a file with dict with proposed configuration - with open("{}.temp".format(VRRP.location['vyos']), 'w') as dict_file: + dirname = os.path.dirname(VRRP.location['vyos']) + makedir(dirname) + with open(VRRP.location['vyos'] + ".temp", 'w') as dict_file: dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups})) return (vrrp_groups, sync_groups) diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf index 9fcabf652..e338b90a2 100644 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ b/src/etc/systemd/system/keepalived.service.d/override.conf @@ -1,2 +1,12 @@ +[Unit] +ConditionPathExists= +ConditionPathExists=/run/keepalived/keepalived.conf +After= +After=vyos-router.service + [Service] KillMode=process +ExecStart= +ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork $DAEMON_ARGS +PIDFile= +PIDFile=/run/keepalived/keepalived.pid diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 7e2076820..1e749207b 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -13,7 +13,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# import os import time @@ -22,11 +21,12 @@ import argparse import threading import re import json -from pathlib import Path -from queue import Queue import logging + +from queue import Queue from logging.handlers import SysLogHandler +from vyos.ifconfig.vrrp import VRRP from vyos.util import cmd # configure logging @@ -60,7 +60,7 @@ class KeepalivedFifo: def _config_load(self): try: # read the dictionary file with configuration - with open('/run/keepalived_config.dict', 'r') as dict_file: + with open(VRRP.location['vyos'], 'r') as dict_file: vrrp_config_dict = json.load(dict_file) self.vrrp_config = {'vrrp_groups': {}, 'sync_groups': {}} # save VRRP instances to the new dictionary @@ -93,8 +93,8 @@ class KeepalivedFifo: # create FIFO pipe def pipe_create(self): - if Path(self.pipe_path).exists(): - logger.info("PIPE already exist: {}".format(self.pipe_path)) + if os.path.exists(self.pipe_path): + logger.info(f"PIPE already exist: {self.pipe_path}") else: os.mkfifo(self.pipe_path) -- cgit v1.2.3 From 260f383221ea1b23e644b0c50f45eeb300e9bc24 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Sep 2021 22:33:07 +0200 Subject: vrrp: keepalived: T616: drop /etc/default/keepalived This is a follow-up commit to 65398e5c8 ("vrrp: keepalived: T616: move configuration to volatile /run directory") as it makes no sense to store a static /etc/default/keepalived file marked as "Autogenerated by VyOS" that only enabled the SNMP option to keepalived. Better pass the --snmp switch via the systemd override file and drop all other references/files. --- data/templates/vrrp/daemon.tmpl | 5 ----- python/vyos/ifconfig/vrrp.py | 1 - src/conf_mode/vrrp.py | 1 - src/etc/systemd/system/keepalived.service.d/override.conf | 2 +- 4 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 data/templates/vrrp/daemon.tmpl (limited to 'python/vyos/ifconfig') diff --git a/data/templates/vrrp/daemon.tmpl b/data/templates/vrrp/daemon.tmpl deleted file mode 100644 index c9dbea72d..000000000 --- a/data/templates/vrrp/daemon.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -# Autogenerated by VyOS -# Options to pass to keepalived - -# DAEMON_ARGS are appended to the keepalived command-line -DAEMON_ARGS="--snmp" diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index 481b0284a..3d6f4d7c6 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -37,7 +37,6 @@ class VRRP(object): 'state': '/tmp/keepalived.data', 'stats': '/tmp/keepalived.stats', 'json': '/tmp/keepalived.json', - 'daemon': '/etc/default/keepalived', 'config': '/run/keepalived/keepalived.conf', 'vyos': '/run/keepalived/keepalived_config.dict', } diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index cee6a9ba2..55c4cc67a 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -231,7 +231,6 @@ def generate(data): render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', {"groups": vrrp_groups, "sync_groups": sync_groups}) - render(VRRP.location['daemon'], 'vrrp/daemon.tmpl', {}) return None diff --git a/src/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf index e338b90a2..c18ae0c29 100644 --- a/src/etc/systemd/system/keepalived.service.d/override.conf +++ b/src/etc/systemd/system/keepalived.service.d/override.conf @@ -7,6 +7,6 @@ After=vyos-router.service [Service] KillMode=process ExecStart= -ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork $DAEMON_ARGS +ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp PIDFile= PIDFile=/run/keepalived/keepalived.pid -- cgit v1.2.3 From 823b03417aa6ac717e785b741541e251c0d4125f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Sep 2021 08:55:36 +0200 Subject: vyos.ifconfig: dhcp: T3300: always re-start dhcp client instead of start Commit dd2eb5e5686655 ("dhcp: T3300: add DHCP default route distance") changed the logic on how the DHCP process is going to be started. The systemd unit was always "started" even if it was already running. It should rather be re-started to track changes in e.g. the DHCP hostname setting. (cherry picked from commit 8ba8f0e097527e3aaaf8b395bfc07cce47e2c788) --- python/vyos/ifconfig/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 89a562cf6..1098df7fd 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1146,7 +1146,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]: -- cgit v1.2.3 From b34b8a8fe3bc4a4a706c22af4a7ef4ea8a75e14b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Sep 2021 08:55:50 +0200 Subject: vyos.ifconfig: dhcpv6: re-use systemd_service definition variable (cherry picked from commit d1c58addd881e06b389799a9c14d8ebf5d03c567) --- python/vyos/ifconfig/interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1098df7fd..76f1e158a 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1163,16 +1163,16 @@ 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: - systemd_service = f'dhcp6c@{ifname}.service' if is_systemd_service_active(systemd_service): self._cmd(f'systemctl stop {systemd_service}') if os.path.isfile(config_file): -- cgit v1.2.3 From e4812d266ea841f8baf5ad6c7cfae1c7eba664b6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Sep 2021 12:28:37 +0200 Subject: vyos.ifconfig: T3860: bugfix in get_mac_synthetic() Commit 081e23996f (vyos.ifconfig: get_mac_synthetic() must generate a stable "MAC") calculated a "stable" synthetic MAC address per the interface based on UUID and the interface name. The problem is that this calculation is too stable when run on multiple instances of VyOS on different hosts/hypervisors. Having R1 and R2 setup a connection both via "tun10" interface will become the same "synthetic" MAC address manifesting in the same link-local IPv6 address. This e.g. breaks OSPFv3 badly as both neighbors communicate using the same link-local address. As workaround one can: set interfaces tunnel tun1337 address 'fe80::1:1337/64' set interfaces tunnel tun1337 ipv6 address no-default-link-local This commit changes the way in how the synthetic MAC address is generated. It's based on the first 48 bits of a sha256 sum build from a CPU ID retrieved via DMI, the MAC address of eth0 and the interface name as used before. This should add enough entropy to get a stable pseudo MAC address. (cherry picked from commit 8d6861290f39298701b0a89bd358545763cee14b) --- python/vyos/ifconfig/interface.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 76f1e158a..709c70b65 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -27,8 +27,6 @@ from netifaces import ifaddresses # this is not the same as socket.AF_INET/INET6 from netifaces import AF_INET from netifaces import AF_INET6 -from uuid import uuid3 -from uuid import NAMESPACE_DNS from vyos import ConfigError from vyos.configdict import list_diff @@ -439,11 +437,22 @@ class Interface(Control): >>> Interface('eth0').get_mac() '00:50:ab:cd:ef:00' """ - # calculate a UUID based on the interface name - this is as predictable - # as an interface MAC address and thus can be used in the same way - tmp = uuid3(NAMESPACE_DNS, self.ifname) - # take the last 48 bits from the UUID string - tmp = str(tmp).split('-')[-1] + 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 -- cgit v1.2.3 From e687502b1cf4a3e15c562a3662afcbe0776b1fe7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 2 Oct 2021 18:50:38 +0200 Subject: vyos.ifconfig: T3883: bugfix VRF deletion We can not pass None as VRF name, this raises an exception. OSError: [Errno 255] failed to run command: ip link set dev eth2 master None --- python/vyos/ifconfig/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 709c70b65..7f712d98f 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1304,7 +1304,7 @@ class Interface(Control): # unbinding will call 'ip link set dev eth0 nomaster' which will # also drop the interface out of a bridge or bond - thus this is # checked before - self.set_vrf(config.get('vrf', None)) + self.set_vrf(config.get('vrf', '')) # Configure ARP cache timeout in milliseconds - has default value tmp = dict_search('ip.arp_cache_timeout', config) -- cgit v1.2.3 From 1786246655c36e932be649a29d70cca6c9a29773 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 9 Oct 2021 08:38:06 +0200 Subject: tunnel: T3894: fix design when building synthetic MAC addresses It seems not all systems have eth0 - get a list of all available Ethernet interfaces on the system (without VLAN subinterfaces) and then take the first one. (cherry picked from commit f19c92f255011149eeb7626a2e158456abe4c9b8) --- python/vyos/ifconfig/interface.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 7f712d98f..036ca1413 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -441,15 +441,19 @@ class Interface(Control): # 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() + + # XXX: T3894 - it seems not all systems have eth0 - get a list of all + # available Ethernet interfaces on the system (without VLAN subinterfaces) + # and then take the first one. + all_eth_ifs = [x for x in Section.interfaces('ethernet') if '.' not in x] + first_mac = Interface(all_eth_ifs[0]).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(first_mac.encode()) sha.update(self.ifname.encode()) # take the most significant 48 bits from the SHA256 string tmp = sha.hexdigest()[:12] -- cgit v1.2.3 From 61e4d75abb1129f63df5a47b9c9bf0553850d893 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 18 Nov 2021 19:10:33 +0100 Subject: wwan: T3620: place interface in A/D state when removed --- python/vyos/ifconfig/wwan.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/wwan.py b/python/vyos/ifconfig/wwan.py index f18959a60..295f8bc2e 100644 --- a/python/vyos/ifconfig/wwan.py +++ b/python/vyos/ifconfig/wwan.py @@ -26,3 +26,19 @@ class WWANIf(Interface): 'eternal': 'wwan[0-9]+$', }, } + + def remove(self): + """ + Remove interface from operating system. Removing the interface + deconfigures all assigned IP addresses. + Example: + >>> from vyos.ifconfig import WWANIf + >>> i = WWANIf('wwan0') + >>> i.remove() + """ + + if self.exists(self.ifname): + # interface is always A/D down. It needs to be enabled explicitly + self.set_admin_state('down') + + super().remove() -- cgit v1.2.3 From 038fb7a4dd7d22c9fc604811509f9a1477d4a89d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 19 Nov 2021 07:36:48 +0100 Subject: wwan: T3620: fix commend in remove() Improve commend in WWANIf.remove() - remove() was implemented in commit 61e4d75a ("wwan: T3620: place interface in A/D state when removed"). (cherry picked from commit d9a19b77a56031fa3fbfa43a85c8be7ee83ae3d7) --- python/vyos/ifconfig/wwan.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/wwan.py b/python/vyos/ifconfig/wwan.py index 295f8bc2e..845c9bef9 100644 --- a/python/vyos/ifconfig/wwan.py +++ b/python/vyos/ifconfig/wwan.py @@ -29,8 +29,8 @@ class WWANIf(Interface): def remove(self): """ - Remove interface from operating system. Removing the interface - deconfigures all assigned IP addresses. + Remove interface from config. Removing the interface deconfigures all + assigned IP addresses. Example: >>> from vyos.ifconfig import WWANIf >>> i = WWANIf('wwan0') @@ -38,7 +38,8 @@ class WWANIf(Interface): """ if self.exists(self.ifname): - # interface is always A/D down. It needs to be enabled explicitly + # interface is placed in A/D state when removed from config! It + # will remain visible for the operating system. self.set_admin_state('down') super().remove() -- cgit v1.2.3 From 783dcc13e22be9d81179ab986062a12ed49cd601 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 19 Nov 2021 07:37:56 +0100 Subject: ethernet: T4011: deleting interface should place interface in admin down state Interface will still be visible to the operating system. (cherry picked from commit a14f93adfa633eabff90524e1f83d56092ec0c3c) --- python/vyos/ifconfig/ethernet.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'python/vyos/ifconfig') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index d06b0a842..4ae350634 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -81,6 +81,23 @@ class EthernetIf(Interface): super().__init__(ifname, **kargs) self.ethtool = Ethtool(ifname) + def remove(self): + """ + Remove interface from config. Removing the interface deconfigures all + assigned IP addresses. + Example: + >>> from vyos.ifconfig import WWANIf + >>> i = EthernetIf('eth0') + >>> i.remove() + """ + + if self.exists(self.ifname): + # interface is placed in A/D state when removed from config! It + # will remain visible for the operating system. + self.set_admin_state('down') + + super().remove() + def set_flow_control(self, enable): """ Changes the pause parameters of the specified Ethernet device. -- cgit v1.2.3