def __init__(self, fn): logger.debug('YamlParser parsing %s', fn) try: with open(fn) as fh: self.ifstates = yaml.load(fh, Loader) except OSError as ex: raise ParserOpenError(ex)
def create(self, do_apply, excpts, oper="add"): logger.info(oper, extra={ 'iface': self.settings['ifname'], 'style': IfStateLogging.STYLE_CHG }) logger.debug("ip link add: {}".format(" ".join( "{}={}".format(k, v) for k, v in self.settings.items()))) if do_apply: try: state = self.settings.pop('state', None) ipr.link('add', **(self.settings)) self.idx = next( iter(ipr.link_lookup(ifname=self.settings['ifname'])), None) if not state is None and not self.idx is None: try: ipr.link('set', index=self.idx, state=state) except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('set', err, state=state) except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('add', err, **(self.settings)) if not self.ethtool is None: logger.debug("ethtool: {}".format(self.ethtool)) self.set_ethtool_state(self.settings['ifname'], self.ethtool.keys(), do_apply)
def __init__(self): logger.debug('IfState {}'.format(__version__)) self.links = {} self.addresses = {} self.neighbours = {} self.ignore = {} self.vrrp = { 'links': [], 'group': {}, 'instance': {}, } self.tables = None self.rules = None self.sysctl = Sysctl() self.tc = {} self.wireguard = {} self.features = { 'link': True, 'sysctl': os.access('/proc/sys/net', os.R_OK), 'ethtool': not ethtool_path is None, 'tc': True, 'wireguard': not globals().get("WireGuard") is None, } logger.debug('{}'.format(' '.join( sorted([x for x, y in self.features.items() if y]))), extra={'iface': 'features'})
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, iface, do_apply): if not iface in self.sysctls: logger.debug("no sysctl settings", extra={'iface': iface}) return for family in self.sysctls[iface].keys(): for key, val in self.sysctls[iface][family].items(): self.set_sysctl(iface, family, key, val, do_apply)
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 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 _matches(r1, r2, fields, indent): for fld in fields: if not indent is None: logger.debug("{}: {} - {}".format(fld, r1.get(fld), r2.get(fld)), extra={'iface': indent}) if fld in r1 and fld in r2: if r1[fld] != r2[fld]: return False elif fld in r1 or fld in r2: return False return True
def recreate(self, do_apply, excpts): logger.debug('has wrong link kind %s, removing', self.settings['kind'], extra={'iface': self.settings['ifname']}) if do_apply: try: ipr.link('del', index=self.idx) except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('del', err) self.idx = None self.create(do_apply, "replace")
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 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 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, 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(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 update(self, do_apply, excpts): logger.debug('checking link', extra={'iface': self.settings['ifname']}) old_state = self.iface['state'] has_link_changes = False has_state_changes = False for setting in self.settings.keys(): logger.debug(' %s: %s => %s', setting, self.get_if_attr(setting), self.settings[setting], extra={'iface': self.settings['ifname']}) if setting == "state": has_state_changes = self.get_if_attr( setting) != self.settings[setting] else: if setting != 'kind' or self.cap_create: has_link_changes |= self.get_if_attr( setting) != self.settings[setting] has_ethtool_changes = set() if not self.ethtool is None: logger.debug('checking ethtool', extra={'iface': self.settings['ifname']}) ethtool = self.get_ethtool_state(self.ethtool.keys()) if ethtool is None: has_ethtool_changes.add(self.ethtool.keys()) else: for setting, options in self.ethtool.items(): if not setting in ethtool: has_ethtool_changes.add(setting) else: for option in options.keys(): logger.debug( ' %s.%s: %s => %s', setting, option, ethtool[setting].get(option), self.ethtool[setting][option], extra={'iface': self.settings['ifname']}) if self.ethtool[setting][option] != ethtool[ setting].get(option): has_ethtool_changes.add(setting) if has_link_changes: logger.debug('needs to be configured', extra={'iface': self.settings['ifname']}) if old_state: logger.debug('shutting down', extra={'iface': self.settings['ifname']}) if do_apply: try: ipr.link('set', index=self.idx, state='down') except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('set', err, state='down') if not 'state' in self.settings: self.settings['state'] = 'up' self.set_ethtool_state(self.get_if_attr('ifname'), has_ethtool_changes, do_apply) if self.get_if_attr('ifname') == self.settings['ifname']: logger.info('change', extra={ 'iface': self.settings['ifname'], 'style': IfStateLogging.STYLE_CHG }) else: logger.info('change (was {})'.format( self.get_if_attr('ifname')), extra={ 'iface': self.settings['ifname'], 'style': IfStateLogging.STYLE_CHG }) if do_apply: try: state = self.settings.pop('state', None) ipr.link('set', index=self.idx, **(self.settings)) except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('set', err, state=state) try: if not state is None: # restore state setting for recreate self.settings['state'] = state ipr.link('set', index=self.idx, state=state) except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('set', err, state=state) else: self.set_ethtool_state(self.get_if_attr('ifname'), has_ethtool_changes, do_apply) if has_state_changes: try: ipr.link('set', index=self.idx, state=self.settings["state"]) except Exception as err: if not isinstance(err, netlinkerror_classes): raise excpts.add('set', err, state=state) logger.info('change', extra={ 'iface': self.settings['ifname'], 'style': IfStateLogging.STYLE_CHG }) else: logger.info('ok', extra={ 'iface': self.settings['ifname'], 'style': IfStateLogging.STYLE_OK })