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 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 __init__(self, name): self.name = name self.str2id = {} self.id2str = {} try: fn = os.path.join('/etc/iproute2', name) with open(fn, 'r') as fp: self._parse(fp) for fn in glob( os.path.join('/etc/iproute2', "{}.d".format(name), "*.conf")): with open(fn, 'r') as fp: self._parse(fp) except: logger.info('could not open {}'.format(fn))
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 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(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 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 })
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)