Example #1
0
 def dump(cls, view):
     req = '''
           SELECT main.f_target,main.f_tflags,%s
           FROM routes AS main
           LEFT JOIN nh AS nh
           ON main.f_route_id = nh.f_route_id
               AND main.f_target = nh.f_target
           ''' % ','.join(
         ['%s' % x for x in _dump_rt + _dump_nh + ['main.f_route_id']])
     header = (['target', 'tflags'] +
               [rtmsg.nla2name(x[7:]) for x in _dump_rt] +
               ['nh_%s' % nh.nla2name(x[5:])
                for x in _dump_nh] + ['metrics', 'encap'])
     yield header
     plch = view.ndb.schema.plch
     where, values = cls._dump_where(view)
     for record in view.ndb.schema.fetch(req + where, values):
         route_id = record[-1]
         record = list(record[:-1])
         #
         # fetch metrics
         metrics = tuple(
             view.ndb.schema.fetch(
                 '''
             SELECT * FROM metrics WHERE f_route_id = %s
         ''' % (plch, ),
                 (route_id, ),
             ))
         if metrics:
             ret = {}
             names = view.ndb.schema.compiled['metrics']['norm_names']
             for k, v in zip(names, metrics[0]):
                 if v is not None and k not in (
                         'target',
                         'route_id',
                         'tflags',
                 ):
                     ret[k] = v
             record.append(json.dumps(ret))
         else:
             record.append(None)
         #
         # fetch encap
         enc_mpls = tuple(
             view.ndb.schema.fetch(
                 '''
             SELECT * FROM enc_mpls WHERE f_route_id = %s
         ''' % (plch, ),
                 (route_id, ),
             ))
         if enc_mpls:
             record.append(enc_mpls[0][2])
         else:
             record.append(None)
         yield record
Example #2
0
    def load_netlink(self, msg):
        with self._direct_state:
            if self['ipdb_scope'] == 'locked':
                # do not touch locked interfaces
                return

            self['ipdb_scope'] = 'system'

            # IPv6 multipath via several devices (not networks) is a very
            # special case, since we get only the first hop notification. Ask
            # the kernel guys why. I've got no idea.
            #
            # So load all the rest
            flags = msg.get('header', {}).get('flags', 0)
            family = msg.get('family', 0)
            clean_mp = True
            table = msg.get_attr('RTA_TABLE') or msg.get('table')
            dst = msg.get_attr('RTA_DST')
            #
            # It MAY be a multipath hop
            #
            if family == AF_INET6 and not msg.get_attr('RTA_MULTIPATH'):
                #
                # It is a notification about the route created
                #
                if flags == NLM_F_CREATE:
                    #
                    # This routine can significantly slow down the IPDB
                    # instance, but I see no way around. Some are born
                    # to endless night.
                    #
                    clean_mp = False
                    msgs = self.nl.route(
                        'show', table=table, dst=dst, family=family
                    )
                    for nhmsg in msgs:
                        nh = type(self)(ipdb=self.ipdb, parent=self)
                        nh.load_netlink(nhmsg)
                        with nh._direct_state:
                            del nh['dst']
                            del nh['ipdb_scope']
                            del nh['ipdb_priority']
                            del nh['multipath']
                            del nh['metrics']
                        self.add_nh(nh)
                #
                # it IS a multipath hop loaded during IPDB init
                #
                elif flags == NLM_F_MULTI and self.get('dst'):
                    nh = type(self)(ipdb=self.ipdb, parent=self)
                    nh.load_netlink(msg)
                    with nh._direct_state:
                        del nh['dst']
                        del nh['ipdb_scope']
                        del nh['ipdb_priority']
                        del nh['multipath']
                        del nh['metrics']
                    self.add_nh(nh)
                    return

            for (key, value) in msg.items():
                self[key] = value

            # cleanup multipath NH
            if clean_mp:
                for nh in self['multipath']:
                    self.del_nh(nh)

            for cell in msg['attrs']:
                #
                # Parse on demand
                #
                norm = rtmsg.nla2name(cell[0])
                if norm in self.cleanup:
                    continue
                value = cell[1]
                # normalize RTAX
                if norm == 'metrics':
                    with self['metrics']._direct_state:
                        for metric in tuple(self['metrics'].keys()):
                            del self['metrics'][metric]
                        for (rtax, rtax_value) in value['attrs']:
                            rtax_norm = rtmsg.metrics.nla2name(rtax)
                            self['metrics'][rtax_norm] = rtax_value
                elif norm == 'multipath':
                    for record in value:
                        nh = type(self)(ipdb=self.ipdb, parent=self)
                        nh.load_netlink(record)
                        with nh._direct_state:
                            del nh['dst']
                            del nh['ipdb_scope']
                            del nh['ipdb_priority']
                            del nh['multipath']
                            del nh['metrics']
                        self['multipath'].add(nh)
                elif norm == 'encap':
                    with self['encap']._direct_state:
                        # WIP: should support encap_types other than MPLS
                        if value.get_attr('MPLS_IPTUNNEL_DST'):
                            ret = []
                            for dst in value.get_attr('MPLS_IPTUNNEL_DST'):
                                ret.append(str(dst['label']))
                            if ret:
                                self['encap']['labels'] = '/'.join(ret)
                elif norm == 'via':
                    with self['via']._direct_state:
                        self['via'] = value
                elif norm == 'newdst':
                    self['newdst'] = [x['label'] for x in value]
                else:
                    self[norm] = value

            if msg.get('family', 0) == AF_MPLS:
                dst = msg.get_attr('RTA_DST')
                if dst:
                    dst = dst[0]['label']
            else:
                if msg.get_attr('RTA_DST'):
                    dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len'])
                else:
                    dst = 'default'
            self['dst'] = dst

            # fix RTA_ENCAP_TYPE if needed
            if msg.get_attr('RTA_ENCAP'):
                if self['encap_type'] is not None:
                    with self['encap']._direct_state:
                        self['encap']['type'] = self['encap_type']
                    self['encap_type'] = None
            # or drop encap, if there is no RTA_ENCAP in msg
            elif self['encap'] is not None:
                self['encap_type'] = None
                with self['encap']._direct_state:
                    self['encap'] = {}

            # drop metrics, if there is no RTA_METRICS in msg
            if not msg.get_attr('RTA_METRICS') and self['metrics'] is not None:
                with self['metrics']._direct_state:
                    self['metrics'] = {}

            # same for via
            if not msg.get_attr('RTA_VIA') and self['via'] is not None:
                with self['via']._direct_state:
                    self['via'] = {}

            # one hop -> multihop transition
            if not msg.get_attr('RTA_GATEWAY') and self['gateway'] is not None:
                self['gateway'] = None
            if (
                'oif' not in msg
                and not msg.get_attr('RTA_OIF')
                and self['oif'] is not None
            ):
                self['oif'] = None

            # finally, cleanup all not needed
            for item in self.cleanup:
                if item in self:
                    del self[item]
Example #3
0
class BaseRoute(Transactional):
    '''
    Persistent transactional route object
    '''

    _fields = [rtmsg.nla2name(i[0]) for i in rtmsg.nla_map]
    for key, _ in rtmsg.fields:
        _fields.append(key)
    _fields.append('removal')
    _virtual_fields = ['ipdb_scope', 'ipdb_priority']
    _fields.extend(_virtual_fields)
    _linked_sets = ['multipath']
    _nested = []
    _gctime = None
    cleanup = ('attrs', 'header', 'event', 'cacheinfo')
    _fields_cmp = {
        'src': _normalize_ipnet,
        'dst': _normalize_ipnet,
        'gateway': _normalize_ipaddr,
        'prefsrc': _normalize_ipaddr,
    }

    def __init__(self, ipdb, mode=None, parent=None, uid=None):
        Transactional.__init__(self, ipdb, mode, parent, uid)
        with self._direct_state:
            self['ipdb_priority'] = 0

    @with_transaction
    def add_nh(self, prime):
        with self._write_lock:
            # if the multipath chain is empty, copy the current
            # nexthop as the first in the multipath
            if not self['multipath']:
                first = {}
                for key in ('oif', 'gateway', 'newdst'):
                    if self[key]:
                        first[key] = self[key]
                if first:
                    if self['family']:
                        first['family'] = self['family']
                    for key in ('encap', 'via', 'metrics'):
                        if self[key] and any(self[key].values()):
                            first[key] = self[key]
                            self[key] = None
                    self['multipath'].add(first)
                    # cleanup key fields
                    for key in ('oif', 'gateway', 'newdst'):
                        self[key] = None
            # add the prime as NH
            if self['family'] == AF_MPLS:
                prime['family'] = AF_MPLS
            self['multipath'].add(prime)

    @with_transaction
    def del_nh(self, prime):
        with self._write_lock:
            if not self['multipath']:
                raise KeyError(
                    'attempt to delete nexthop from ' 'non-multipath route'
                )
            nh = dict(prime)
            if self['family'] == AF_MPLS:
                nh['family'] = AF_MPLS
            self['multipath'].remove(nh)

    def load_netlink(self, msg):
        with self._direct_state:
            if self['ipdb_scope'] == 'locked':
                # do not touch locked interfaces
                return

            self['ipdb_scope'] = 'system'

            # IPv6 multipath via several devices (not networks) is a very
            # special case, since we get only the first hop notification. Ask
            # the kernel guys why. I've got no idea.
            #
            # So load all the rest
            flags = msg.get('header', {}).get('flags', 0)
            family = msg.get('family', 0)
            clean_mp = True
            table = msg.get_attr('RTA_TABLE') or msg.get('table')
            dst = msg.get_attr('RTA_DST')
            #
            # It MAY be a multipath hop
            #
            if family == AF_INET6 and not msg.get_attr('RTA_MULTIPATH'):
                #
                # It is a notification about the route created
                #
                if flags == NLM_F_CREATE:
                    #
                    # This routine can significantly slow down the IPDB
                    # instance, but I see no way around. Some are born
                    # to endless night.
                    #
                    clean_mp = False
                    msgs = self.nl.route(
                        'show', table=table, dst=dst, family=family
                    )
                    for nhmsg in msgs:
                        nh = type(self)(ipdb=self.ipdb, parent=self)
                        nh.load_netlink(nhmsg)
                        with nh._direct_state:
                            del nh['dst']
                            del nh['ipdb_scope']
                            del nh['ipdb_priority']
                            del nh['multipath']
                            del nh['metrics']
                        self.add_nh(nh)
                #
                # it IS a multipath hop loaded during IPDB init
                #
                elif flags == NLM_F_MULTI and self.get('dst'):
                    nh = type(self)(ipdb=self.ipdb, parent=self)
                    nh.load_netlink(msg)
                    with nh._direct_state:
                        del nh['dst']
                        del nh['ipdb_scope']
                        del nh['ipdb_priority']
                        del nh['multipath']
                        del nh['metrics']
                    self.add_nh(nh)
                    return

            for (key, value) in msg.items():
                self[key] = value

            # cleanup multipath NH
            if clean_mp:
                for nh in self['multipath']:
                    self.del_nh(nh)

            for cell in msg['attrs']:
                #
                # Parse on demand
                #
                norm = rtmsg.nla2name(cell[0])
                if norm in self.cleanup:
                    continue
                value = cell[1]
                # normalize RTAX
                if norm == 'metrics':
                    with self['metrics']._direct_state:
                        for metric in tuple(self['metrics'].keys()):
                            del self['metrics'][metric]
                        for (rtax, rtax_value) in value['attrs']:
                            rtax_norm = rtmsg.metrics.nla2name(rtax)
                            self['metrics'][rtax_norm] = rtax_value
                elif norm == 'multipath':
                    for record in value:
                        nh = type(self)(ipdb=self.ipdb, parent=self)
                        nh.load_netlink(record)
                        with nh._direct_state:
                            del nh['dst']
                            del nh['ipdb_scope']
                            del nh['ipdb_priority']
                            del nh['multipath']
                            del nh['metrics']
                        self['multipath'].add(nh)
                elif norm == 'encap':
                    with self['encap']._direct_state:
                        # WIP: should support encap_types other than MPLS
                        if value.get_attr('MPLS_IPTUNNEL_DST'):
                            ret = []
                            for dst in value.get_attr('MPLS_IPTUNNEL_DST'):
                                ret.append(str(dst['label']))
                            if ret:
                                self['encap']['labels'] = '/'.join(ret)
                elif norm == 'via':
                    with self['via']._direct_state:
                        self['via'] = value
                elif norm == 'newdst':
                    self['newdst'] = [x['label'] for x in value]
                else:
                    self[norm] = value

            if msg.get('family', 0) == AF_MPLS:
                dst = msg.get_attr('RTA_DST')
                if dst:
                    dst = dst[0]['label']
            else:
                if msg.get_attr('RTA_DST'):
                    dst = '%s/%s' % (msg.get_attr('RTA_DST'), msg['dst_len'])
                else:
                    dst = 'default'
            self['dst'] = dst

            # fix RTA_ENCAP_TYPE if needed
            if msg.get_attr('RTA_ENCAP'):
                if self['encap_type'] is not None:
                    with self['encap']._direct_state:
                        self['encap']['type'] = self['encap_type']
                    self['encap_type'] = None
            # or drop encap, if there is no RTA_ENCAP in msg
            elif self['encap'] is not None:
                self['encap_type'] = None
                with self['encap']._direct_state:
                    self['encap'] = {}

            # drop metrics, if there is no RTA_METRICS in msg
            if not msg.get_attr('RTA_METRICS') and self['metrics'] is not None:
                with self['metrics']._direct_state:
                    self['metrics'] = {}

            # same for via
            if not msg.get_attr('RTA_VIA') and self['via'] is not None:
                with self['via']._direct_state:
                    self['via'] = {}

            # one hop -> multihop transition
            if not msg.get_attr('RTA_GATEWAY') and self['gateway'] is not None:
                self['gateway'] = None
            if (
                'oif' not in msg
                and not msg.get_attr('RTA_OIF')
                and self['oif'] is not None
            ):
                self['oif'] = None

            # finally, cleanup all not needed
            for item in self.cleanup:
                if item in self:
                    del self[item]

    def commit(
        self, tid=None, transaction=None, commit_phase=1, commit_mask=0xFF
    ):

        if not commit_phase & commit_mask:
            return self

        error = None
        drop = self.ipdb.txdrop
        devop = 'set'
        cleanup = []
        # FIXME -- make a debug object
        debug = {'traceback': None, 'next_stage': None}
        notx = True

        if tid or transaction:
            notx = False

        if tid:
            transaction = self.global_tx[tid]
        else:
            transaction = transaction or self.current_tx

        # ignore global rollbacks on invalid routes
        if self['ipdb_scope'] == 'create' and commit_phase > 1:
            return

        # create a new route
        if self['ipdb_scope'] != 'system':
            devop = 'add'

        # work on an existing route
        snapshot = self.pick()
        added, removed = transaction // snapshot
        added.pop('ipdb_scope', None)
        removed.pop('ipdb_scope', None)

        try:
            # route set
            if self['family'] != AF_MPLS:
                cleanup = [
                    any(snapshot['metrics'].values())
                    and not any(added.get('metrics', {}).values()),
                    any(snapshot['encap'].values())
                    and not any(added.get('encap', {}).values()),
                ]
            if (
                any(added.values())
                or any(cleanup)
                or removed.get('multipath', None)
                or devop == 'add'
            ):
                # prepare multipath target sync
                wlist = []
                if transaction['multipath']:
                    mplen = len(transaction['multipath'])
                    if mplen == 1:
                        # set up local targets
                        for nh in transaction['multipath']:
                            for key in ('oif', 'gateway', 'newdst'):
                                if nh.get(key, None):
                                    self.set_target(key, nh[key])
                                    wlist.append(key)
                        mpt = None
                    else:

                        def mpcheck(mpset):
                            return len(mpset) == mplen

                        mpt = self['multipath'].set_target(mpcheck, True)
                else:
                    mpt = None

                # prepare the anchor key to catch *possible* route update
                old_key = self.make_key(self)
                new_key = self.make_key(transaction)
                if old_key != new_key:
                    # assume we can not move routes between tables (yet ;)
                    if self['family'] == AF_MPLS:
                        route_index = self.ipdb.routes.tables['mpls'].idx
                    else:
                        route_index = self.ipdb.routes.tables[
                            self['table'] or 254
                        ].idx
                    # re-link the route record
                    if new_key in route_index:
                        raise CommitException('route idx conflict')
                    else:
                        route_index[new_key] = {'key': new_key, 'route': self}
                    # wipe the old key, if needed
                    if old_key in route_index:
                        del route_index[old_key]
                self.nl.route(devop, **transaction)
                # delete old record, if required
                if (old_key != new_key) and (devop == 'set'):
                    req = dict(old_key._asdict())
                    # update the request with the scope.
                    #
                    # though the scope isn't a part of the
                    # key, it is required for the correct
                    # removal -- only if it is set
                    req['scope'] = self.get('scope', 0)
                    self.nl.route('del', **req)
                transaction.wait_all_targets()
                for key in ('metrics', 'via'):
                    if transaction[key] and transaction[key]._targets:
                        transaction[key].wait_all_targets()
                if mpt is not None:
                    mpt.wait(SYNC_TIMEOUT)
                    if not mpt.is_set():
                        raise CommitException('multipath target is not set')
                    self['multipath'].clear_target(mpt)
                for key in wlist:
                    self.wait_target(key)
            # route removal
            if (transaction['ipdb_scope'] in ('shadow', 'remove')) or (
                (transaction['ipdb_scope'] == 'create') and commit_phase == 2
            ):
                if transaction['ipdb_scope'] == 'shadow':
                    with self._direct_state:
                        self['ipdb_scope'] = 'locked'
                # create watchdog
                wd = self.ipdb.watchdog(
                    'RTM_DELROUTE', **self.wd_key(snapshot)
                )
                for route in self.nl.route('delete', **snapshot):
                    self.ipdb.routes.load_netlink(route)
                wd.wait()
                if transaction['ipdb_scope'] == 'shadow':
                    with self._direct_state:
                        self['ipdb_scope'] = 'shadow'

            # success, so it's safe to drop the transaction
            drop = True

        except Exception as e:

            error = e
            # prepare postmortem
            debug['traceback'] = traceback.format_exc()
            debug['error_stack'] = []
            debug['next_stage'] = None

            if commit_phase == 1:
                try:
                    self.commit(
                        transaction=snapshot,
                        commit_phase=2,
                        commit_mask=commit_mask,
                    )
                except Exception as i_e:
                    debug['next_stage'] = i_e
                    error = RuntimeError()

        if drop and notx:
            self.drop(transaction.uid)

        if error is not None:
            error.debug = debug
            raise error

        self.ipdb.routes.gc()
        return self

    def remove(self):
        self['ipdb_scope'] = 'remove'
        return self

    def shadow(self):
        self['ipdb_scope'] = 'shadow'
        return self

    def detach(self):
        if self.get('family') == AF_MPLS:
            table = 'mpls'
        else:
            table = self.get('table', 254)
        del self.ipdb.routes.tables[table][self.make_key(self)]