def apply(self, do_apply): excpts = ExceptionCollector(ifname=self.iface) # get ifindex self.idx = next(iter(ipr.link_lookup(ifname=self.iface)), None) if self.idx == None: logger.warning('link missing', extra={'iface': self.iface}) return changes = [] ipr_qdiscs = None # apply ingress qdics if "ingress" in self.tc: ipr_qdiscs = ipr.get_qdiscs(index=self.idx) if self.apply_ingress(self.tc["ingress"], self.get_qdisc( ipr_qdiscs, TC.INGRESS_PARENT), excpts, do_apply): changes.append("ingress") # apply qdisc tree if "qdisc" in self.tc: if ipr_qdiscs is None: ipr_qdiscs = ipr.get_qdiscs(index=self.idx) logger.debug('checking qdisc tree', extra={'iface': self.iface}) if self.apply_qtree( self.tc["qdisc"], self.get_qdisc(ipr_qdiscs, TC.ROOT_HANDLE), ipr_qdiscs, TC.ROOT_HANDLE, excpts, do_apply): changes.append("qdisc") # apply filters if "filter" in self.tc: ipr_filters = ipr.get_filters( index=self.idx) + ipr.get_filters(index=self.idx, parent=TC.INGRESS_HANDLE) logger.debug('checking filters', extra={'iface': self.iface}) if self.apply_filter( self.tc["filter"], ipr_filters, excpts, do_apply): changes.append("filter") if len(changes) > 0: logger.info( 'change ({})'.format(", ".join(changes)), extra={'iface': self.iface, 'style': IfStateLogging.STYLE_CHG}) else: logger.info( 'ok', extra={'iface': self.iface, 'style': IfStateLogging.STYLE_OK}) return excpts
def apply(self, ignores, do_apply): logger.info('\nconfiguring routing rules...') krules = self.kernel_rules() for rule in self.rules: found = False for i, krule in enumerate(krules): if rule_matches(rule, krule, rule.keys()): del krules[i] found = True break if found: logger.info('ok', extra={ 'iface': '#{}'.format(rule['priority']), 'style': IfStateLogging.STYLE_OK }) else: logger.info('add', extra={ 'iface': '#{}'.format(rule['priority']), 'style': IfStateLogging.STYLE_CHG }) logger.debug("ip rule add: {}".format(" ".join( "{}={}".format(k, v) for k, v in rule.items()))) try: if do_apply: ipr.rule('add', **rule) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('rule setup failed: {}'.format(err.args[1])) for rule in krules: ignore = False for irule in ignores: if 'proto' in irule: irule['protocol'] = irule['proto'] del irule['proto'] if rule_matches(rule, irule, irule.keys()): ignore = True break if ignore: continue logger.info('del', extra={ 'iface': '#{}'.format(rule['priority']), 'style': IfStateLogging.STYLE_DEL }) try: if do_apply: ipr.rule('del', **rule) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('removing rule failed: {}'.format(err.args[1]))
def apply_qtree(self, tc, qdisc, ipr_qdiscs, parent, excpts, do_apply, recreate=False): if qdisc is None: recreate = True if not recreate: logger.debug(' kind: {} => {}'.format( qdisc.get_attr("TCA_KIND"), tc["kind"]), extra={'iface': self.iface}) if tc["kind"] != qdisc.get_attr("TCA_KIND"): recreate = True else: logger.debug(' handle: {} => {}'.format( TC.int2handle(qdisc["handle"]), tc["handle"]), extra={'iface': self.iface}) if TC.handle2int(tc["handle"]) != qdisc["handle"]: recreate = True if recreate and do_apply: if qdisc is not None: try: ipr.tc("del", index=self.idx, parent=qdisc["parent"]) except: pass opts = { "index": self.idx, "parent": TC.int2handle(parent), } for k, v in tc.items(): if k != "children": opts[k] = v if do_apply: if recreate: try: ipr.tc("add", **opts) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('adding qdisc {} on {} failed: {}'.format( opts.get("handle"), self.iface, err.args[1])) excpts.add('add', err, **opts) else: # soft update for TCA_OPTIONS try: ipr.tc("change", **opts) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('updating qdisc {} on {} failed: {}'.format( opts.get("handle"), self.iface, err.args[1])) excpts.add('change', err, **opts) changes = recreate if "children" in tc: for i in range(len(tc["children"])): changes = changes or self.apply_qtree(tc["children"][i], self.get_qchild( ipr_qdiscs, qdisc["handle"], i+1), ipr_qdiscs, TC.handle2int(tc["handle"]) | i + 1, do_apply, recreate) return changes
def add(self, op, excpt, **kwargs): self.excpts.append({ 'op': op, 'excpt': excpt, 'args': kwargs, }) if not self.quiet: logger.warning('{} link {} failed: {}'.format( op, self.ifname, excpt.args[1]))
def get_ethtool_state(self, settings): ethtool = {} for setting in settings: ethtool[setting] = {} fn = self.get_ethtool_fn(setting) if not os.path.isfile(fn): logger.debug('no prior ethtool %s state available', setting, extra={'iface': self.settings['ifname']}) continue try: with open(fn) as fh: obj = yaml.load(fh, Loader=yaml.SafeLoader) if type(obj) == dict: ethtool[setting] = obj except Exception as err: logger.warning('parsing {} failed: {}'.format(fn, err)) return ethtool
def set_sysctl(self, iface, family, key, val, do_apply): fn = '/proc/sys/net/{}/conf/{}/{}'.format(family, iface, key) with open(fn) as fh: current = fh.readline().rstrip() if current == str(val): logger.info('ok', extra={ 'iface': "{}/{}".format(family, key), 'style': IfStateLogging.STYLE_OK }) else: logger.info('set', extra={ 'iface': "{}/{}".format(family, key), 'style': IfStateLogging.STYLE_CHG }) if do_apply: try: with open(fn, 'w') as fh: fh.writelines([str(val)]) except OSError as err: logger.warning('updating sysctl {}/{} failed: {}'.format( family, key, err.args[1]))
def apply_ingress(self, ingress, qdisc, excpts, do_apply): logger.debug('checking ingress qdisc', extra={'iface': self.iface}) if not ingress: if qdisc: if do_apply: opts = { "index": self.idx, "parent": TC.INGRESS_PARENT, } try: ipr.tc("del", **opts) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('removing ingress qdisc on {} failed: {}'.format( self.iface, err.args[1])) excpts.add('del', err, **opts) return True else: if not qdisc: if do_apply: opts = { "index": self.idx, "kind": "ingress", } try: ipr.tc("add", **opts) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('adding ingress qdisc on {} failed: {}'.format( self.iface, err.args[1])) excpts.add('add', err, **opts) return True return False
def apply(self, do_apply): logger.debug('getting neighbours', extra={'iface': self.iface}) # get ifindex idx = next(iter(ipr.link_lookup(ifname=self.iface)), None) if idx == None: logger.warning('link missing', extra={'iface': self.iface}) return # get neighbour entries (only NUD_PERMANENT) ipr_neigh = {} neigh_add = {} for neigh in ipr.get_neighbours(ifindex=idx, state=128): ip = ip_address(neigh.get_attr('NDA_DST')) ipr_neigh[ip] = neigh.get_attr('NDA_LLADDR') for ip, lladdr in self.neighbours.items(): if ip in ipr_neigh and lladdr == ipr_neigh[ip]: logger.info(' %s', ip, extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_OK}) del ipr_neigh[ip] else: neigh_add[ip] = lladdr for ip, lladdr in ipr_neigh.items(): logger.info( '-%s', str(ip), extra={'iface': self.iface, 'style': IfStateLogging.STYLE_DEL}) try: if do_apply: ipr.neigh("del", ifindex=idx, dst=str( ip)) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('removing neighbour {} failed: {}'.format( str(ip), err.args[1])) for ip, lladdr in neigh_add.items(): logger.info('+%s', str(ip), extra={'iface': self.iface, 'style': IfStateLogging.STYLE_CHG}) if do_apply: try: opts = { 'ifindex': idx, 'dst': str(ip), 'lladdr': lladdr, 'state': 128 } ipr.neigh('replace', **opts) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('adding neighbour {} failed: {}'.format( str(ip), err.args[1]))
def set_ethtool_state(self, ifname, settings, do_apply): if len(settings) == 0: return logger.info('change (ethtool)', extra={ 'iface': self.settings['ifname'], 'style': IfStateLogging.STYLE_CHG }) if not do_apply: return for setting in settings: cmd = [ethtool_path] if setting in ['coalesce', 'features', 'pause', 'rxfh']: cmd.append("--{}".format(setting)) elif setting in ['nfc']: cmd.append("--config-{}".format(setting)) else: cmd.append("--set-{}".format(setting)) cmd.append(ifname) for option, value in self.ethtool[setting].items(): cmd.extend([option] + self.fmt_ethtool_opt(value)) logger.debug("{}".format(" ".join(cmd))) try: res = subprocess.run(cmd) if res.returncode != 0: logger.warning('`{}` has failed'.format(" ".join( cmd[0:3]))) return except Exception as err: logger.warning('failed to run `{}`: {}'.format( " ".join(cmd[0:3]), err.args[1])) return fn = self.get_ethtool_fn(setting) try: with open(fn, 'w') as fh: yaml.dump(self.ethtool[setting], fh) except Exception as err: logger.warning('failed write `{}`: {}'.format(fn, err.args[1]))
def _apply(self, do_apply, vrrp_type, vrrp_name, vrrp_state): vrrp_ignore = [] vrrp_remove = [] by_vrrp = not None in [vrrp_type, vrrp_name, vrrp_state] for ifname, link in self.links.items(): if ifname in self.vrrp['links']: if not by_vrrp: vrrp_ignore.append(ifname) else: if not link.match_vrrp_select(vrrp_type, vrrp_name): vrrp_ignore.append(ifname) elif not vrrp_name in self.vrrp[ vrrp_type] or not vrrp_state in self.vrrp[ vrrp_type][ vrrp_name] or not ifname in self.vrrp[ vrrp_type][vrrp_name][vrrp_state]: vrrp_remove.append(ifname) elif by_vrrp: vrrp_ignore.append(ifname) self.ipaddr_ignore = set() for ip in self.ignore.get('ipaddr', []): self.ipaddr_ignore.add(ip_network(ip)) if not any(not x is None for x in self.links.values()): logger.error("DANGER: Not a single link config has been found!") raise LinkNoConfigFound() for iface in ['all', 'default']: if self.sysctl.has_settings(iface): logger.info("\nconfiguring {} interface sysctl".format(iface)) self.sysctl.apply(iface, do_apply) for stage in range(2): if stage == 0: logger.info("\nconfiguring interface links") for ifname in vrrp_remove: logger.debug('to be removed due to vrrp constraint', extra={'iface': ifname}) del self.links[ifname] else: logger.info("\nconfiguring interface links (stage 2)") retry = False applied = [] while len(applied) + len(vrrp_ignore) < len(self.links): last = len(applied) for name, link in self.links.items(): if name in applied: continue if name in vrrp_ignore: logger.debug('skipped due to vrrp constraint', extra={'iface': name}) continue if link is None: logger.debug('skipped due to no link settings', extra={'iface': name}) applied.append(name) else: deps = link.depends() if all(x in applied for x in deps): self.sysctl.apply(name, do_apply) excpts = link.apply(do_apply) if excpts.has_errno(errno.EEXIST): retry = True applied.append(name) if last == len(applied): raise LinkCircularLinked() for link in ipr.get_links(): name = link.get_attr('IFLA_IFNAME') # skip links on ignore list if not name in self.links and not any( re.match(regex, name) for regex in self.ignore.get('ifname', [])): info = link.get_attr('IFLA_LINKINFO') # remove virtual interface if info is not None: kind = info.get_attr('IFLA_INFO_KIND') logger.info('del', extra={ 'iface': name, 'style': IfStateLogging.STYLE_DEL }) if do_apply: try: ipr.link('set', index=link.get('index'), state='down') ipr.link('del', index=link.get('index')) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning( 'removing link {} failed: {}'.format( name, err.args[1])) # shutdown physical interfaces else: if name in vrrp_ignore: logger.warning('vrrp', extra={ 'iface': name, 'style': IfStateLogging.STYLE_OK }) if link.get('state') == 'down': logger.warning('orphan', extra={ 'iface': name, 'style': IfStateLogging.STYLE_OK }) else: logger.warning('orphan', extra={ 'iface': name, 'style': IfStateLogging.STYLE_CHG }) if do_apply: try: ipr.link('set', index=link.get('index'), state='down') except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning( 'updating link {} failed: {}'.format( name, err.args[1])) if not retry: break if any(not x is None for x in self.tc.values()): logger.info("\nconfiguring interface traffic control...") for name, tc in self.tc.items(): if name in vrrp_ignore: logger.debug('skipped due to vrrp constraint', extra={'iface': name}) elif tc is None: logger.debug('skipped due to no tc settings', extra={'iface': name}) else: tc.apply(do_apply) if any(not x is None for x in self.addresses.values()): logger.info("\nconfiguring interface ip addresses...") # add empty objects for unhandled interfaces for link in ipr.get_links(): name = link.get_attr('IFLA_IFNAME') # skip links on ignore list if not name in self.addresses and not any( re.match(regex, name) for regex in self.ignore.get('ifname', [])): self.addresses[name] = Addresses(name, []) for name, addresses in self.addresses.items(): if name in vrrp_ignore: logger.debug('skipped due to vrrp constraint', extra={'iface': name}) elif addresses is None: logger.debug('skipped due to no address settings', extra={'iface': name}) else: addresses.apply(self.ipaddr_ignore, self.ignore.get('ipaddr_dynamic', True), do_apply) else: logger.info("\nno interface ip addressing to be applied") if any(not x is None for x in self.neighbours.values()): logger.info("\nconfiguring interface neighbours...") # add empty objects for unhandled interfaces for link in ipr.get_links(): name = link.get_attr('IFLA_IFNAME') # skip links on ignore list if not name in self.neighbours and not any( re.match(regex, name) for regex in self.ignore.get('ifname', [])): self.neighbours[name] = Neighbours(name, []) for name, neighbours in self.neighbours.items(): if name in vrrp_ignore: logger.debug('skipped due to vrrp constraint', extra={'iface': name}) elif neighbours is None: logger.debug('skipped due to no address settings', extra={'iface': name}) else: neighbours.apply(do_apply) else: logger.info("\nno interface neighbours to be applied") if not self.tables is None: self.tables.apply(self.ignore.get('routes', []), do_apply) if not self.rules is None: self.rules.apply(self.ignore.get('rules', []), do_apply) if len(self.wireguard): logger.info("\nconfiguring WireGuard...") for iface, wireguard in self.wireguard.items(): if iface in vrrp_ignore: logger.debug('skipped due to vrrp constraint', extra={'iface': name}) continue wireguard.apply(do_apply)
def update(self, ifstates, soft_schema): # check config schema schema = json.loads( pkgutil.get_data("libifstate", "../schema/ifstate.conf.schema.json")) try: validate(ifstates, schema, format_checker=FormatChecker()) except ValidationError as ex: if len(ex.path) > 0: path = ["$"] for i, p in enumerate(ex.absolute_path): if type(p) == int: path.append("[{}]".format(p)) else: path.append(".") path.append(p) detail = "{}: {}".format("".join(path), ex.message) else: detail = ex.message if soft_schema: logger.error("Config validation failed for {}".format(detail)) else: raise ParserValidationError(detail) # parse options if 'options' in ifstates: # parse global sysctl settings if 'sysctl' in ifstates['options']: for iface in ['all', 'default']: if iface in ifstates['options']['sysctl']: self.sysctl.add(iface, ifstates['options']['sysctl'][iface]) # add interfaces from config for ifstate in ifstates['interfaces']: name = ifstate['name'] if name in self.links: raise LinkDuplicate() if 'link' in ifstate: self.links[name] = Link(name, ifstate['link'], ifstate.get('ethtool'), ifstate.get('vrrp')) else: self.links[name] = None if 'addresses' in ifstate: self.addresses[name] = Addresses(name, ifstate['addresses']) else: self.addresses[name] = None if 'neighbours' in ifstate: self.neighbours[name] = Neighbours(name, ifstate['neighbours']) else: self.neighbours[name] = None if 'vrrp' in ifstate: ktype = ifstate['vrrp']['type'] kname = ifstate['vrrp']['name'] kstates = ifstate['vrrp']['states'] if not kname in self.vrrp[ktype]: self.vrrp[ktype][kname] = {} for kstate in kstates: if not kstate in self.vrrp[ktype][kname]: self.vrrp[ktype][kname][kstate] = [] self.vrrp[ktype][kname][kstate].append(name) self.vrrp['links'].append(name) if 'sysctl' in ifstate: self.sysctl.add(name, ifstate['sysctl']) if 'cshaper' in ifstate: profile_name = ifstate['cshaper'].get('profile', 'default') logger.debug('cshaper profile {} enabled'.format(profile_name), extra={'iface': name}) cshaper_profile = ifstates['cshaper'][profile_name] # ingress ifb_name = re.sub(cshaper_profile['ingress_ifname']['search'], cshaper_profile['ingress_ifname']['replace'], name) logger.debug('cshaper ifb name {}'.format(ifb_name), extra={'iface': name}) ifb_state = { 'name': ifb_name, 'link': { 'state': 'up', 'kind': 'ifb', }, 'tc': { 'qdisc': cshaper_profile['ingress_qdisc'], } } ifb_state['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get( 'ingress', 'unlimited') ifstates['interfaces'].append(ifb_state) # egress if 'tc' in ifstate: logger.warning('cshaper settings replaces tc settings', extra={'iface': name}) ifstate['tc'] = { 'ingress': True, 'qdisc': cshaper_profile['egress_qdisc'], 'filter': [{ 'kind': 'matchall', 'parent': 'ffff:', 'action': [{ 'kind': 'mirred', 'direction': 'egress', 'action': 'redirect', 'dev': ifb_name, }] }] } ifstate['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get( 'egress', 'unlimited') del ifstate['cshaper'] if 'tc' in ifstate: self.tc[name] = TC(name, ifstate['tc']) if 'wireguard' in ifstate: if not self.features['wireguard']: raise FeatureMissingError("wireguard") self.wireguard[name] = WireGuard(name, ifstate['wireguard']) # add routing from config if 'routing' in ifstates: if 'routes' in ifstates['routing']: if self.tables is None: self.tables = Tables() for route in ifstates['routing']['routes']: self.tables.add(route) if 'rules' in ifstates['routing']: if self.rules is None: self.rules = Rules() for rule in ifstates['routing']['rules']: self.rules.add(rule) # add ignore list items self.ignore.update(ifstates['ignore'])
def apply_filter(self, tc, ipr_filters, excpts, do_apply): tc_filters = {} # assign prio numbers if missing for i in range(len(tc)): if not "prio" in tc[i]: tc[i]["prio"] = 0xc001 - len(tc) + i parent = TC.handle2int(tc[i].get("parent", 0)) if not parent in tc_filters: tc_filters[parent] = {} tc_filters[parent][tc[i]["prio"]] = tc[i] changes = False # remove unreferenced filters removed = {} for ipr_filter in ipr_filters: prio = ipr_filter["info"] >> 16 parent = TC.handle2int(ipr_filter.get("parent", 0)) rm = not parent in tc_filters rm |= parent in tc_filters and \ prio not in tc_filters[parent] if rm and prio not in removed.get(parent, []): changes = True if not parent in removed: removed[parent] = [] removed[parent].append(prio) if do_apply: opts = { "index": self.idx, "info": ipr_filter["info"], "parent": parent, } try: ipr.del_filter_by_info(**opts) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('deleting filter #{} on {} failed: {}'.format( prio, self.iface, err.args[1])) excpts.add('del', err, **opts) if do_apply: for parent in tc_filters.keys(): for tc_filter in tc_filters[parent].values(): tc_filter['index'] = self.idx if "action" in tc_filter: for action in tc_filter["action"]: if action["kind"] == "mirred": # get ifindex action["ifindex"] = next( iter(ipr.link_lookup(ifname=action["dev"])), None) if self.idx == None: logger.warning("filter #{} references unknown interface {}".format( tc_filter["prio"], action["dev"]), extra={'iface': self.iface}) continue if "parent" in tc_filter: tc_filter["parent"] = TC.handle2int(tc_filter["parent"]) try: try: ipr.tc("replace-filter", **tc_filter) # replace seems only to work if there is no filter # => something has changed changes = True except Exception as err: if not isinstance(err, netlinkerror_classes): raise # replace does not work, supress changes result # for now #changes = True opts = { "index": self.idx, "info": tc_filter["prio"] << 16, "parent": parent, } ipr.del_filter_by_info(**opts) ipr.tc("add-filter", **tc_filter) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('replace filter #{} on {} failed: {}'.format( tc_filter['prio'], self.iface, err.args[1])) excpts.add('replace', err, **tc_filter) return changes
def apply(self, ignore, ign_dynamic, do_apply): logger.debug('getting addresses', extra={'iface': self.iface}) # get ifindex idx = next(iter(ipr.link_lookup(ifname=self.iface)), None) if idx == None: logger.warning('link missing', extra={'iface': self.iface}) return # get active ip addresses ipr_addr = {} addr_add = [] for addr in ipr.get_addr(index=idx): ip = ip_interface( addr.get_attr('IFA_ADDRESS') + '/' + str(addr['prefixlen'])) ipr_addr[ip] = addr for addr in self.addresses: if addr in ipr_addr: logger.info(' %s', addr.with_prefixlen, extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_OK }) del ipr_addr[addr] else: addr_add.append(addr) for ip, addr in ipr_addr.items(): if not any(ip in net for net in ignore): if not ign_dynamic or ipr_addr[ip][ 'flags'] & IFA_F_PERMANENT == IFA_F_PERMANENT: logger.info('-%s', ip.with_prefixlen, extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_DEL }) try: if do_apply: ipr.addr("del", index=idx, address=str(ip.ip), mask=ip.network.prefixlen) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('removing ip {}/{} failed: {}'.format( str(ip.ip), ip.network.prefixlen, err.args[1])) for addr in addr_add: logger.info('+%s', addr.with_prefixlen, extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_CHG }) if do_apply: try: ipr.addr("add", index=idx, address=str(addr.ip), mask=addr.network.prefixlen) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('adding ip {}/{} failed: {}'.format( str(addr.ip), addr.network.prefixlen, err.args[1]))
def apply(self, ignores, do_apply): for table, croutes in self.tables.items(): pfx = RTLookups.tables.lookup_str(table) logger.info('\nconfiguring routing table {}...'.format(pfx)) kroutes = self.kernel_routes(table) for route in croutes: if 'oif' in route and type(route['oif']) == str: route['oif'] = next( iter(ipr.link_lookup(ifname=route['oif'])), None) found = False identical = False for i, kroute in enumerate(kroutes): if route_matches(route, kroute): del kroutes[i] found = True if route_matches(route, kroute, route.keys(), indent=route['dst']): identical = True break if identical: logger.info('ok', extra={ 'iface': route['dst'], 'style': IfStateLogging.STYLE_OK }) else: if found: logger.info('change', extra={ 'iface': route['dst'], 'style': IfStateLogging.STYLE_CHG }) else: logger.info('add', extra={ 'iface': route['dst'], 'style': IfStateLogging.STYLE_CHG }) logger.debug("ip route replace: {}".format(" ".join( "{}={}".format(k, v) for k, v in route.items()))) try: if do_apply: ipr.route('replace', **route) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('route setup {} failed: {}'.format( route['dst'], err.args[1])) for route in kroutes: ignore = False for iroute in ignores: if route_matches(route, iroute, iroute.keys()): ignore = True break if ignore: continue logger.info('del', extra={ 'iface': route['dst'], 'style': IfStateLogging.STYLE_DEL }) try: if do_apply: ipr.route('del', **route) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('removing route {} failed: {}'.format( route['dst'], err.args[1]))
def apply(self, do_apply): # get kernel's wireguard settings for the interface try: state = wg.get_interface( self.iface, spill_private_key=True, spill_preshared_keys=True) except Exception as err: logger.warning('WireGuard on {} failed: {}'.format( self.iface, err.args[1])) return # check base settings (not the peers, yet) has_changes = False for setting in [x for x in self.wireguard.keys() if x != 'peers']: logger.debug(' %s: %s => %s', setting, getattr( state, setting), self.wireguard[setting], extra={'iface': self.iface}) has_changes |= self.wireguard[setting] != getattr(state, setting) if has_changes: logger.info('change [iface]', extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_CHG}) if do_apply: try: wg.set_interface( self.iface, **{k: v for k, v in self.wireguard.items() if k != "peers"}) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('updating iface {} failed: {}'.format( self.iface, err.args[1])) else: logger.info('ok [iface]', extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_OK}) # check peers list if provided if 'peers' in self.wireguard: peers = getattr(state, 'peers') has_pchanges = False avail = [] for peer in self.wireguard['peers']: avail.append(peer['public_key']) pubkey = next( iter([x for x in peers.keys() if x == peer['public_key']]), None) if pubkey is None: has_pchanges = True if do_apply: try: wg.set_peer(self.iface, **peer) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('add peer to {} failed: {}'.format( self.iface, err.args[1])) else: pchange = False for setting in peer.keys(): attr = getattr(peers[pubkey], setting) if setting == 'allowedips': attr = set(attr) logger.debug(' peer.%s: %s => %s', setting, attr, peer[setting], extra={'iface': self.iface}) if type(attr) == set: pchange |= not (attr == peer[setting]) else: pchange |= str(peer[setting]) != str(getattr( peers[pubkey], setting)) if pchange: has_pchanges = True if do_apply: try: wg.set_peer(self.iface, **peer) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('change peer at {} failed: {}'.format( self.iface, err.args[1])) for peer in peers: if not peer in avail: has_pchanges = True if do_apply: try: wg.remove_peers(self.iface, peer) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('remove peer from {} failed: {}'.format( self.iface, err.args[1])) if has_pchanges: logger.info('change [peers]', extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_CHG}) else: logger.info('ok [peers]', extra={ 'iface': self.iface, 'style': IfStateLogging.STYLE_OK})
def show(self, showall=False): if showall: defaults = deepcopy(Parser._default_ifstates) else: defaults = {} ipaddr_ignore = [] for ip in Parser._default_ifstates['ignore']['ipaddr_builtin']: ipaddr_ignore.append(ip_network(ip)) ifs_links = [] for ipr_link in ipr.get_links(): name = ipr_link.get_attr('IFLA_IFNAME') # skip links on ignore list if not any( re.match(regex, name) for regex in Parser._default_ifstates['ignore']['ifname_builtin']): ifs_link = { 'name': name, 'addresses': [], 'link': { 'state': ipr_link['state'], }, } for addr in ipr.get_addr(index=ipr_link['index']): if addr['flags'] & IFA_F_PERMANENT == IFA_F_PERMANENT: ip = ip_interface( addr.get_attr('IFA_ADDRESS') + '/' + str(addr['prefixlen'])) if not any(ip in net for net in ipaddr_ignore): ifs_link['addresses'].append(ip.with_prefixlen) info = ipr_link.get_attr('IFLA_LINKINFO') if info is None: kind = None else: kind = info.get_attr('IFLA_INFO_KIND') if kind is not None: ifs_link['link']['kind'] = kind data = info.get_attr('IFLA_INFO_DATA') # unsupported link type, fallback to raw encoding if data is not None and type(data) != str: for k, v in data['attrs']: if k not in ['UNKNOWN', 'IFLA_VLAN_FLAGS']: attr = ipr_link.nla2name(k) if attr in Link.attr_value_maps: ifs_link['link'][ attr] = Link.attr_value_maps[attr].get( v, v) else: ifs_link['link'][attr] = v else: ifs_link['link']['kind'] = 'physical' addr = ipr_link.get_attr('IFLA_ADDRESS') if not addr is None: ifs_link['link']['address'] = addr permaddr = ipr.get_permaddr(name) if not permaddr is None: if addr is None: ifs_link['link']['addr'] = permaddr elif addr != permaddr: ifs_link['link']['permaddr'] = permaddr businfo = ipr.get_businfo(name) if not businfo is None: ifs_link['link']['businfo'] = businfo for attr in [ 'link', 'master', 'gre_link', 'ip6gre_link', 'vxlan_link', 'xfrm_link' ]: ref = ipr_link.get_attr('IFLA_{}'.format(attr.upper())) if ref is not None: try: ifs_link['link'][attr] = ipr.get_ifname_by_index( ref) except Exception as err: if not isinstance(err, netlinkerror_classes): raise logger.warning('lookup {} failed: {}'.format( attr, err.args[1]), extra={'iface': name}) ifs_link['link'][attr] = ref mtu = ipr_link.get_attr('IFLA_MTU') if not mtu is None and not mtu in [1500, 65536]: ifs_link['link']['mtu'] = mtu ifs_links.append(ifs_link) routing = { 'routes': Tables().show_routes( Parser._default_ifstates['ignore']['routes_builtin']), 'rules': Rules().show_rules( Parser._default_ifstates['ignore']['rules_builtin']), } return {**defaults, **{'interfaces': ifs_links, 'routing': routing}}
def create(self, do_apply, oper="add"): logger.warning('Unable to create missing physical link: {}'.format( self.settings.get('ifname')))