From d5f0e51d2208fa7b1b3ff8e8a9bde74a85c91028 Mon Sep 17 00:00:00 2001 From: Adrien Banlin Date: Mon, 12 Jun 2023 10:33:07 +0200 Subject: [PATCH 1/6] ifupdown.utils: simplify expand_iface_range Replace indexing by variables named start/end and prefix/suffix. --- ifupdown2/ifupdown/utils.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/ifupdown2/ifupdown/utils.py b/ifupdown2/ifupdown/utils.py index 05c7e48e..1af7e0f1 100644 --- a/ifupdown2/ifupdown/utils.py +++ b/ifupdown2/ifupdown/utils.py @@ -256,18 +256,12 @@ def parse_iface_range(cls, name): @classmethod def expand_iface_range(cls, name): - ifacenames = [] - irange = cls.parse_iface_range(name) - if irange: - if len(irange) == 3: - # eg swp1.[2-4], r = "swp1.", 2, 4) - for i in range(irange[1], irange[2]): - ifacenames.append('%s%d' %(irange[0], i)) - elif len(irange) == 4: - for i in range(irange[1], irange[2]): - # eg swp[2-4].100, r = ("swp", 2, 4, ".100") - ifacenames.append('%s%d%s' %(irange[0], i, irange[3])) - return ifacenames + ifrange = cls.parse_iface_range(name) + if not ifrange: + return [] + prefix, start, end = ifrange[0], ifrange[1], ifrange[2] + suffix = '' if len(ifrange) <= 3 else ifrange[3] + return [f'{prefix}{i}{suffix}' for i in range(start, end)] @classmethod def is_ifname_range(cls, name): From 7cebbec15535d3539ff63eadf1497c4c9a895423 Mon Sep 17 00:00:00 2001 From: Adrien Banlin Date: Mon, 12 Jun 2023 10:36:00 +0200 Subject: [PATCH 2/6] ifupdown.utils: fix itf range in argument ifquery excluded the last digit of interfaces range given. ex: eth[1-2] would give only eth1 instead of eth1 + eth2. This commit fix this behavior by increasing the range in expand_iface_range. --- ifupdown2/ifupdown/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifupdown2/ifupdown/utils.py b/ifupdown2/ifupdown/utils.py index 1af7e0f1..0d0732b3 100644 --- a/ifupdown2/ifupdown/utils.py +++ b/ifupdown2/ifupdown/utils.py @@ -261,7 +261,7 @@ def expand_iface_range(cls, name): return [] prefix, start, end = ifrange[0], ifrange[1], ifrange[2] suffix = '' if len(ifrange) <= 3 else ifrange[3] - return [f'{prefix}{i}{suffix}' for i in range(start, end)] + return [f'{prefix}{i}{suffix}' for i in range(start, end + 1)] @classmethod def is_ifname_range(cls, name): From eb92c380422a18289b072e9e38e1cf91dfeb0c9e Mon Sep 17 00:00:00 2001 From: Adrien Banlin Date: Mon, 12 Jun 2023 10:42:41 +0200 Subject: [PATCH 3/6] networkinterfaces: make auto alias of allow-auto The commit make the auto_ifaces container pointing to the allow_classes['auto'] list. (since it's a mutable object, we get the same instance) --- ifupdown2/ifupdown/networkinterfaces.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ifupdown2/ifupdown/networkinterfaces.py b/ifupdown2/ifupdown/networkinterfaces.py index 2bebe398..ffa62ab2 100644 --- a/ifupdown2/ifupdown/networkinterfaces.py +++ b/ifupdown2/ifupdown/networkinterfaces.py @@ -53,7 +53,6 @@ def __init__(self, interfacesfile='/etc/network/interfaces', Raises: AttributeError, KeyError """ - self.auto_ifaces = [] self.callbacks = {} self.auto_all = False self.raw = raw @@ -62,7 +61,9 @@ def __init__(self, interfacesfile='/etc/network/interfaces', self.callbacks = {'iface_found' : None, 'validateifaceattr' : None, 'validateifaceobj' : None} - self.allow_classes = {} + self.allow_classes = {'auto': []} + # auto is only an aliases of allow-auto + self.auto_ifaces = self.allow_classes['auto'] self.interfacesfile = interfacesfile self.interfacesfileiobuf = interfacesfileiobuf self.interfacesfileformat = interfacesfileformat From 65b3f523f9a193ad7dd6edac9fb196fc986a9822 Mon Sep 17 00:00:00 2001 From: Adrien Banlin Date: Mon, 12 Jun 2023 10:49:48 +0200 Subject: [PATCH 4/6] networkinterfaces: make allow/auto behave the same This change goal is to make auto behave like allow-auto. This commit will also provide the interfaces range capability to any other allow-class names. --- ifupdown2/ifupdown/networkinterfaces.py | 40 +++++++++++-------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/ifupdown2/ifupdown/networkinterfaces.py b/ifupdown2/ifupdown/networkinterfaces.py index ffa62ab2..f4d10088 100644 --- a/ifupdown2/ifupdown/networkinterfaces.py +++ b/ifupdown2/ifupdown/networkinterfaces.py @@ -142,6 +142,20 @@ def ignore_line(self, line): return 1 return 0 + def _add_ifaces_to_class(self, classname, ifaces): + if classname not in self.allow_classes: + self.allow_classes[classname] = [] + + # This is a specific uses case: everything is considered auto if all + # is being given to the auto or allow-auto classe. + if classname == 'auto' and 'all' in ifaces: + self.auto_all = True + return # nothing is to be done. + + for ifname in ifaces: + ifnames = utils.expand_iface_range(ifname) or [ifname] + self.allow_classes[classname].extend(ifnames) + def process_allow(self, lines, cur_idx, lineno): allow_line = lines[cur_idx] @@ -152,12 +166,7 @@ def process_allow(self, lines, cur_idx, lineno): allow_class = words[0].split('-')[1] ifacenames = words[1:] - - if self.allow_classes.get(allow_class): - for i in ifacenames: - self.allow_classes[allow_class].append(i) - else: - self.allow_classes[allow_class] = ifacenames + self._add_ifaces_to_class(allow_class, ifacenames) return 0 def process_source(self, lines, cur_idx, lineno): @@ -201,25 +210,12 @@ def process_source_directory(self, lines, cur_idx, lineno): def process_auto(self, lines, cur_idx, lineno): auto_ifaces = re.split(self._ws_split_regex, lines[cur_idx])[1:] + if not auto_ifaces: self._parse_error(self._currentfile, lineno, 'invalid auto line \'%s\''%lines[cur_idx]) - return 0 - for a in auto_ifaces: - if a == 'all': - self.auto_all = True - break - r = utils.parse_iface_range(a) - if r: - if len(r) == 3: - # eg swp1.[2-4], r = "swp1.", 2, 4) - for i in range(r[1], r[2]+1): - self.auto_ifaces.append('%s%d' %(r[0], i)) - elif len(r) == 4: - for i in range(r[1], r[2]+1): - # eg swp[2-4].100, r = ("swp", 2, 4, ".100") - self.auto_ifaces.append('%s%d%s' %(r[0], i, r[3])) - self.auto_ifaces.append(a) + else: + self._add_ifaces_to_class('auto', auto_ifaces) return 0 def _add_to_iface_config(self, ifacename, iface_config, attrname, From a2d18a9d78b9a1f44059afa6bbddb2c12ba89d71 Mon Sep 17 00:00:00 2001 From: Adrien Banlin Date: Mon, 12 Jun 2023 12:08:02 +0200 Subject: [PATCH 5/6] networkinterfaces: clean process_iface/vlan code The only real change is the creation of a ifaceobj before testing it's name. (The ifaceobj will still not be added if deemed invalid) --- ifupdown2/ifupdown/networkinterfaces.py | 80 +++++++++---------------- 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/ifupdown2/ifupdown/networkinterfaces.py b/ifupdown2/ifupdown/networkinterfaces.py index f4d10088..1ffd79f1 100644 --- a/ifupdown2/ifupdown/networkinterfaces.py +++ b/ifupdown2/ifupdown/networkinterfaces.py @@ -341,69 +341,45 @@ def _create_ifaceobj_clone(self, ifaceobj, newifaceobjname, return ifaceobj_new + def _clone_iface_range(self, iface_range, iface_orig, iftype=None): + iftype = iftype or iface_orig.type + for name in iface_range: + flags = iface.IFACERANGE_ENTRY + if name == iface_range[0]: + flags |= iface.IFACERANGE_START + obj = self._create_ifaceobj_clone(iface_orig, name, iftype, flags) + yield obj + def process_iface(self, lines, cur_idx, lineno): ifaceobj = iface() lines_consumed = self.parse_iface(lines, cur_idx, lineno, ifaceobj) + found_cb = self.callbacks['iface_found'] + ifrange = utils.expand_iface_range(ifaceobj.name) - range_val = utils.parse_iface_range(ifaceobj.name) - if range_val: - if len(range_val) == 3: - for v in range(range_val[1], range_val[2]+1): - ifacename = '%s%d' %(range_val[0], v) - if utils.check_ifname_size_invalid(ifacename): - self._parse_warn(self._currentfile, lineno, - '%s: interface name too long' %ifacename) - flags = iface.IFACERANGE_ENTRY - if v == range_val[1]: - flags |= iface.IFACERANGE_START - ifaceobj_new = self._create_ifaceobj_clone(ifaceobj, - ifacename, ifaceobj.type, flags) - self.callbacks.get('iface_found')(ifaceobj_new) - elif len(range_val) == 4: - for v in range(range_val[1], range_val[2]+1): - ifacename = '%s%d%s' %(range_val[0], v, range_val[3]) - if utils.check_ifname_size_invalid(ifacename): - self._parse_warn(self._currentfile, lineno, - '%s: interface name too long' %ifacename) - flags = iface.IFACERANGE_ENTRY - if v == range_val[1]: - flags |= iface.IFACERANGE_START - ifaceobj_new = self._create_ifaceobj_clone(ifaceobj, - ifacename, ifaceobj.type, flags) - self.callbacks.get('iface_found')(ifaceobj_new) - else: - self.callbacks.get('iface_found')(ifaceobj) + if not ifrange: + found_cb(ifaceobj) + + for ifclone in self._clone_iface_range(ifrange, ifaceobj): + if utils.check_ifname_size_invalid(ifclone.name): + self._parse_warn(self._currentfile, lineno, + f'{ifclone.name}: interface name too long') + found_cb(ifclone) return lines_consumed # Return next index def process_vlan(self, lines, cur_idx, lineno): ifaceobj = iface() lines_consumed = self.parse_iface(lines, cur_idx, lineno, ifaceobj) + found_cb = self.callbacks['iface_found'] + ifrange = utils.expand_iface_range(ifaceobj.name) + iftype = ifaceType.BRIDGE_VLAN - range_val = utils.parse_iface_range(ifaceobj.name) - if range_val: - if len(range_val) == 3: - for v in range(range_val[1], range_val[2]+1): - flags = iface.IFACERANGE_ENTRY - if v == range_val[1]: - flags |= iface.IFACERANGE_START - ifaceobj_new = self._create_ifaceobj_clone(ifaceobj, - '%s%d' %(range_val[0], v), - ifaceType.BRIDGE_VLAN, flags) - self.callbacks.get('iface_found')(ifaceobj_new) - elif len(range_val) == 4: - for v in range(range_val[1], range_val[2]+1): - flags = iface.IFACERANGE_ENTRY - if v == range_val[1]: - flags |= iface.IFACERANGE_START - ifaceobj_new = self._create_ifaceobj_clone(ifaceobj, - '%s%d%s' %(range_val[0], v, range_val[3]), - ifaceType.BRIDGE_VLAN, - flags) - self.callbacks.get('iface_found')(ifaceobj_new) - else: - ifaceobj.type = ifaceType.BRIDGE_VLAN - self.callbacks.get('iface_found')(ifaceobj) + if not ifrange: + ifaceobj.type = iftype + found_cb(ifaceobj) + + for ifclone in self._clone_iface_range(ifrange, ifaceobj, iftype): + found_cb(ifclone) return lines_consumed # Return next index From cb8c67bc669a0c9be600049f11a1c178021ceed0 Mon Sep 17 00:00:00 2001 From: Adrien Banlin Date: Mon, 12 Jun 2023 15:19:09 +0200 Subject: [PATCH 6/6] networkinterfaces: fix bad allow keyword This commit fix the following by making an understandable error msg: * the 'allow eth0' would make a IndexError shown to the user. * the 'allow-' would be valid and use an empty classname. --- ifupdown2/ifupdown/networkinterfaces.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ifupdown2/ifupdown/networkinterfaces.py b/ifupdown2/ifupdown/networkinterfaces.py index 1ffd79f1..55f93fae 100644 --- a/ifupdown2/ifupdown/networkinterfaces.py +++ b/ifupdown2/ifupdown/networkinterfaces.py @@ -160,12 +160,13 @@ def process_allow(self, lines, cur_idx, lineno): allow_line = lines[cur_idx] words = re.split(self._ws_split_regex, allow_line) - if len(words) <= 1: - raise Exception('invalid allow line \'%s\' at line %d' - %(allow_line, lineno)) - - allow_class = words[0].split('-')[1] - ifacenames = words[1:] + try: + allow_class = words[0].split('-')[1] + ifacenames = words[1:] + if not ifacenames or not allow_class: + raise IndexError() + except IndexError: + raise Exception(f'invalid allow line {allow_line} at line {lineno}') self._add_ifaces_to_class(allow_class, ifacenames) return 0