summaryrefslogtreecommitdiff
path: root/python/vyos/ifconfig/interface.py
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-09-19 21:08:40 +0200
committerChristian Poessinger <christian@poessinger.com>2020-09-19 21:08:40 +0200
commitd1c9ee33f25e45cea0d01f9685f99c960ed4d7f8 (patch)
tree72fdd97c9050f0e77a7a5bc744de3266e4058cac /python/vyos/ifconfig/interface.py
parent14c754f8bd6c96165d8ad3745c19c80a562910e1 (diff)
downloadvyos-1x-d1c9ee33f25e45cea0d01f9685f99c960ed4d7f8.tar.gz
vyos-1x-d1c9ee33f25e45cea0d01f9685f99c960ed4d7f8.zip
ifconfig: T2653: convert VLAN interfaces do discrete class
Instead of using an Adapter pattern to make interfaces VLAN-aware, create a derived class named VLANIf to represent a VLAN. This change was necessary to eliminate mixed code in Interfaces class which was VLAN - free, but recently gained some VLAN specific code for set_admin_state(). In addition this "autoresolves" the issue in T2894 as a bond vlan interface will no longer change the lower interface.
Diffstat (limited to 'python/vyos/ifconfig/interface.py')
-rw-r--r--python/vyos/ifconfig/interface.py190
1 files changed, 146 insertions, 44 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index ffe69f61b..0774235b6 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -86,10 +86,6 @@ class Interface(Control):
'shellcmd': 'ip -json link show dev {ifname}',
'format': lambda j: 'up' if 'UP' in jmespath.search('[*].flags | [0]', json.loads(j)) else 'down',
},
- 'vlan_protocol': {
- 'shellcmd': 'ip -json -details link show dev {ifname}',
- 'format': lambda j: jmespath.search('[*].linkinfo.info_data.protocol | [0]', json.loads(j)),
- },
}
_command_set = {
@@ -562,17 +558,6 @@ class Interface(Control):
"""
self.set_interface('alias', ifalias)
- def get_vlan_protocol(self):
- """
- Retrieve VLAN protocol in use, this can be 802.1Q, 802.1ad or None
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0.10').get_vlan_protocol()
- '802.1Q'
- """
- return self.get_interface('vlan_protocol')
-
def get_admin_state(self):
"""
Get interface administrative state. Function will return 'up' or 'down'
@@ -594,17 +579,6 @@ class Interface(Control):
>>> Interface('eth0').get_admin_state()
'down'
"""
- # A VLAN interface can only be placed in admin up state when
- # the lower interface is up, too
- if self.get_vlan_protocol():
- lower_interface = glob(f'/sys/class/net/{self.ifname}/lower*/flags')[0]
- with open(lower_interface, 'r') as f:
- flags = f.read()
- # If parent is not up - bail out as we can not bring up the VLAN.
- # Flags are defined in kernel source include/uapi/linux/if.h
- if not int(flags, 16) & 1:
- return None
-
if state == 'up':
self._admin_state_down_cnt -= 1
if self._admin_state_down_cnt < 1:
@@ -1031,33 +1005,161 @@ class Interface(Control):
self.add_to_bridge(bridge)
# remove no longer required 802.1ad (Q-in-Q VLANs)
+ ifname = config['ifname']
for vif_s_id in config.get('vif_s_remove', {}):
- self.del_vlan(vif_s_id)
+ vif_s_ifname = f'{ifname}.{vif_s_id}'
+ VLANIf(vif_s_ifname).remove()
# create/update 802.1ad (Q-in-Q VLANs)
- ifname = config['ifname']
- for vif_s_id, vif_s in config.get('vif_s', {}).items():
- tmp=get_ethertype(vif_s.get('ethertype', '0x88A8'))
- s_vlan = self.add_vlan(vif_s_id, ethertype=tmp)
- vif_s['ifname'] = f'{ifname}.{vif_s_id}'
- s_vlan.update(vif_s)
+ for vif_s_id, vif_s_config in config.get('vif_s', {}).items():
+ tmp = deepcopy(VLANIf.get_config())
+ tmp['ethertype'] = get_ethertype(vif_s_config.get('ethertype', '0x88A8'))
+ tmp['source_interface'] = ifname
+ tmp['vlan_id'] = vif_s_id
+
+ vif_s_ifname = f'{ifname}.{vif_s_id}'
+ vif_s_config['ifname'] = vif_s_ifname
+ s_vlan = VLANIf(vif_s_ifname, **tmp)
+ s_vlan.update(vif_s_config)
# remove no longer required client VLAN (vif-c)
- for vif_c_id in vif_s.get('vif_c_remove', {}):
- s_vlan.del_vlan(vif_c_id)
+ for vif_c_id in vif_s_config.get('vif_c_remove', {}):
+ vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}'
+ VLANIf(vif_c_ifname).remove()
# create/update client VLAN (vif-c) interface
- for vif_c_id, vif_c in vif_s.get('vif_c', {}).items():
- c_vlan = s_vlan.add_vlan(vif_c_id)
- vif_c['ifname'] = f'{ifname}.{vif_s_id}.{vif_c_id}'
- c_vlan.update(vif_c)
+ for vif_c_id, vif_c_config in vif_s_config.get('vif_c', {}).items():
+ tmp = deepcopy(VLANIf.get_config())
+ tmp['source_interface'] = vif_s_ifname
+ tmp['vlan_id'] = vif_c_id
+
+ vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}'
+ vif_c_config['ifname'] = vif_c_ifname
+ c_vlan = VLANIf(vif_c_ifname, **tmp)
+ c_vlan.update(vif_c_config)
# remove no longer required 802.1q VLAN interfaces
for vif_id in config.get('vif_remove', {}):
- self.del_vlan(vif_id)
+ vif_ifname = f'{ifname}.{vif_id}'
+ VLANIf(vif_ifname).remove()
# create/update 802.1q VLAN interfaces
- for vif_id, vif in config.get('vif', {}).items():
- vlan = self.add_vlan(vif_id)
- vif['ifname'] = f'{ifname}.{vif_id}'
- vlan.update(vif)
+ for vif_id, vif_config in config.get('vif', {}).items():
+ tmp = deepcopy(VLANIf.get_config())
+ tmp['source_interface'] = ifname
+ tmp['vlan_id'] = vif_id
+
+ vif_ifname = f'{ifname}.{vif_id}'
+ vif_config['ifname'] = vif_ifname
+ vlan = VLANIf(vif_ifname, **tmp)
+ vlan.update(vif_config)
+
+
+class VLANIf(Interface):
+ """ Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """
+ default = {
+ 'type': 'vlan',
+ 'source_interface': '',
+ 'vlan_id': '',
+ 'ethertype': '',
+ 'ingress_qos': '',
+ 'egress_qos': '',
+ }
+
+ options = Interface.options + \
+ ['source_interface', 'vlan_id', 'ethertype', 'ingress_qos', 'egress_qos']
+
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # Do we have sub interfaces (VLANs)? As interfaces need to be deleted
+ # "in order" starting from Q-in-Q we delete them first.
+ for upper in glob(f'/sys/class/net/{self.ifname}/upper*'):
+ # an upper interface could be named: upper_bond0.1000.1100, thus
+ # we need top drop the upper_ prefix
+ vif_c = os.path.basename(upper)
+ vif_c = vif_c.replace('upper_', '')
+ VLANIf(vif_c).remove()
+
+ super().remove()
+
+ def _create(self):
+ # bail out early if interface already exists
+ if os.path.exists(f'/sys/class/net/{self.ifname}'):
+ return
+
+ cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}'
+ if self.config['ethertype']:
+ cmd += ' proto {ethertype}'
+ if self.config['ingress_qos']:
+ cmd += ' ingress-qos-map {ingress_qos}'
+ if self.config['egress_qos']:
+ cmd += ' egress-qos-map {egress_qos}'
+
+ self._cmd(cmd.format(**self.config))
+
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_admin_state('down')
+
+ @staticmethod
+ def get_config():
+ """
+ MACsec interfaces require a configuration when they are added using
+ iproute2. This static method will provide the configuration dictionary
+ used by this class.
+
+ Example:
+ >> dict = MACsecIf().get_config()
+ """
+ config = deepcopy(__class__.default)
+ del config['type']
+ return config
+
+ def set_admin_state(self, state):
+ """
+ Set interface administrative state to be 'up' or 'down'
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_admin_state('down')
+ >>> Interface('eth0').get_admin_state()
+ 'down'
+ """
+ # A VLAN interface can only be placed in admin up state when
+ # the lower interface is up, too
+ lower_interface = glob(f'/sys/class/net/{self.ifname}/lower*/flags')[0]
+ with open(lower_interface, 'r') as f:
+ flags = f.read()
+ # If parent is not up - bail out as we can not bring up the VLAN.
+ # Flags are defined in kernel source include/uapi/linux/if.h
+ if not int(flags, 16) & 1:
+ return None
+
+ return super().set_admin_state(state)
+
+ def update(self, config):
+ """ General helper function which works on a dictionary retrived by
+ get_config_dict(). It's main intention is to consolidate the scattered
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ # call base class first
+ super().update(config)
+
+ # Enable/Disable of an interface must always be done at the end of the
+ # derived class to make use of the ref-counting set_admin_state()
+ # function. We will only enable the interface if 'up' was called as
+ # often as 'down'. This is required by some interface implementations
+ # as certain parameters can only be changed when the interface is
+ # in admin-down state. This ensures the link does not flap during
+ # reconfiguration.
+ state = 'down' if 'disable' in config else 'up'
+ self.set_admin_state(state)