Example #1
0
 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)
Example #2
0
    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)
Example #3
0
    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'})
Example #4
0
    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
Example #5
0
    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)
Example #6
0
    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]))
Example #7
0
    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
Example #8
0
    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]))
Example #9
0
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
Example #10
0
 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")
Example #11
0
    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]))
Example #12
0
    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
Example #13
0
    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
Example #14
0
    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)
Example #15
0
    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'])
Example #16
0
    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]))
Example #17
0
    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]))
Example #18
0
    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})
Example #19
0
    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
                            })