Home | History | Annotate | Download | only in route
      1 #
      2 # Copyright (c) 2011 Thomas Graf <tgraf (at] suug.ch>
      3 #
      4 from __future__ import absolute_import
      5 
      6 __all__ = [
      7     'TcCache',
      8     'Tc',
      9     'QdiscCache',
     10     'Qdisc',
     11     'TcClassCache',
     12     'TcClass',
     13 ]
     14 
     15 from .. import core as netlink
     16 from .  import capi as capi
     17 from .. import util as util
     18 from .  import link as Link
     19 
     20 TC_PACKETS = 0
     21 TC_BYTES = 1
     22 TC_RATE_BPS = 2
     23 TC_RATE_PPS = 3
     24 TC_QLEN = 4
     25 TC_BACKLOG = 5
     26 TC_DROPS = 6
     27 TC_REQUEUES = 7
     28 TC_OVERLIMITS = 9
     29 
     30 TC_H_ROOT = 0xFFFFFFFF
     31 TC_H_INGRESS = 0xFFFFFFF1
     32 
     33 STAT_PACKETS = 0
     34 STAT_BYTES = 1
     35 STAT_RATE_BPS = 2
     36 STAT_RATE_PPS = 3
     37 STAT_QLEN = 4
     38 STAT_BACKLOG = 5
     39 STAT_DROPS = 6
     40 STAT_REQUEUES = 7
     41 STAT_OVERLIMITS = 8
     42 STAT_MAX = STAT_OVERLIMITS
     43 
     44 
     45 class Handle(object):
     46     """ Traffic control handle
     47 
     48     Representation of a traffic control handle which uniquely identifies
     49     each traffic control object in its link namespace.
     50 
     51     handle = tc.Handle('10:20')
     52     handle = tc.handle('root')
     53     print int(handle)
     54     print str(handle)
     55     """
     56     def __init__(self, val=None):
     57         if type(val) is str:
     58             val = capi.tc_str2handle(val)
     59         elif not val:
     60             val = 0
     61 
     62         self._val = int(val)
     63 
     64     def __cmp__(self, other):
     65         if other is None:
     66             other = 0
     67 
     68         if isinstance(other, Handle):
     69             return int(self) - int(other)
     70         elif isinstance(other, int):
     71             return int(self) - other
     72         else:
     73             raise TypeError()
     74 
     75     def __int__(self):
     76         return self._val
     77 
     78     def __str__(self):
     79         return capi.rtnl_tc_handle2str(self._val, 64)[0]
     80 
     81     def isroot(self):
     82         return self._val == TC_H_ROOT or self._val == TC_H_INGRESS
     83 
     84 class TcCache(netlink.Cache):
     85     """Cache of traffic control object"""
     86 
     87     def __getitem__(self, key):
     88         raise NotImplementedError()
     89 
     90 class Tc(netlink.Object):
     91     def __cmp__(self, other):
     92         diff = self.ifindex - other.ifindex
     93         if diff == 0:
     94             diff = int(self.handle) - int(other.handle)
     95         return diff
     96 
     97     def _tc_module_lookup(self):
     98         self._module_lookup(self._module_path + self.kind,
     99                     'init_' + self._name)
    100 
    101     @property
    102     def root(self):
    103         """True if tc object is a root object"""
    104         return self.parent.isroot()
    105 
    106     @property
    107     def ifindex(self):
    108         """interface index"""
    109         return capi.rtnl_tc_get_ifindex(self._rtnl_tc)
    110 
    111     @ifindex.setter
    112     def ifindex(self, value):
    113         capi.rtnl_tc_set_ifindex(self._rtnl_tc, int(value))
    114 
    115     @property
    116     def link(self):
    117         link = capi.rtnl_tc_get_link(self._rtnl_tc)
    118         if not link:
    119             return None
    120 
    121         return Link.Link.from_capi(link)
    122 
    123     @link.setter
    124     def link(self, value):
    125         capi.rtnl_tc_set_link(self._rtnl_tc, value._link)
    126 
    127     @property
    128     def mtu(self):
    129         return capi.rtnl_tc_get_mtu(self._rtnl_tc)
    130 
    131     @mtu.setter
    132     def mtu(self, value):
    133         capi.rtnl_tc_set_mtu(self._rtnl_tc, int(value))
    134 
    135     @property
    136     def mpu(self):
    137         return capi.rtnl_tc_get_mpu(self._rtnl_tc)
    138 
    139     @mpu.setter
    140     def mpu(self, value):
    141         capi.rtnl_tc_set_mpu(self._rtnl_tc, int(value))
    142 
    143     @property
    144     def overhead(self):
    145         return capi.rtnl_tc_get_overhead(self._rtnl_tc)
    146 
    147     @overhead.setter
    148     def overhead(self, value):
    149         capi.rtnl_tc_set_overhead(self._rtnl_tc, int(value))
    150 
    151     @property
    152     def linktype(self):
    153         return capi.rtnl_tc_get_linktype(self._rtnl_tc)
    154 
    155     @linktype.setter
    156     def linktype(self, value):
    157         capi.rtnl_tc_set_linktype(self._rtnl_tc, int(value))
    158 
    159     @property
    160     @netlink.nlattr(fmt=util.handle)
    161     def handle(self):
    162         return Handle(capi.rtnl_tc_get_handle(self._rtnl_tc))
    163 
    164     @handle.setter
    165     def handle(self, value):
    166         capi.rtnl_tc_set_handle(self._rtnl_tc, int(value))
    167 
    168     @property
    169     @netlink.nlattr(fmt=util.handle)
    170     def parent(self):
    171         return Handle(capi.rtnl_tc_get_parent(self._rtnl_tc))
    172 
    173     @parent.setter
    174     def parent(self, value):
    175         capi.rtnl_tc_set_parent(self._rtnl_tc, int(value))
    176 
    177     @property
    178     @netlink.nlattr(fmt=util.bold)
    179     def kind(self):
    180         return capi.rtnl_tc_get_kind(self._rtnl_tc)
    181 
    182     @kind.setter
    183     def kind(self, value):
    184         capi.rtnl_tc_set_kind(self._rtnl_tc, value)
    185         self._tc_module_lookup()
    186 
    187     def get_stat(self, id):
    188         return capi.rtnl_tc_get_stat(self._rtnl_tc, id)
    189 
    190     @property
    191     def _dev(self):
    192         buf = util.kw('dev') + ' '
    193 
    194         if self.link:
    195             return buf + util.string(self.link.name)
    196         else:
    197             return buf + util.num(self.ifindex)
    198 
    199     def brief(self, title, nodev=False, noparent=False):
    200         ret = title + ' {a|kind} {a|handle}'
    201 
    202         if not nodev:
    203             ret += ' {a|_dev}'
    204 
    205         if not noparent:
    206             ret += ' {t|parent}'
    207 
    208         return ret + self._module_brief()
    209 
    210     @staticmethod
    211     def details():
    212         return '{t|mtu} {t|mpu} {t|overhead} {t|linktype}'
    213 
    214     @property
    215     def packets(self):
    216         return self.get_stat(STAT_PACKETS)
    217 
    218     @property
    219     def bytes(self):
    220         return self.get_stat(STAT_BYTES)
    221 
    222     @property
    223     def qlen(self):
    224         return self.get_stat(STAT_QLEN)
    225 
    226     @staticmethod
    227     def stats(fmt):
    228         return fmt.nl('{t|packets} {t|bytes} {t|qlen}')
    229 
    230 class QdiscCache(netlink.Cache):
    231     """Cache of qdiscs"""
    232 
    233     def __init__(self, cache=None):
    234         if not cache:
    235             cache = self._alloc_cache_name('route/qdisc')
    236 
    237         self._protocol = netlink.NETLINK_ROUTE
    238         self._nl_cache = cache
    239 
    240 #	def __getitem__(self, key):
    241 #        	if type(key) is int:
    242 #                        link = capi.rtnl_link_get(self._this, key)
    243 #                elif type(key) is str:
    244 #                        link = capi.rtnl_link_get_by_name(self._this, key)
    245 #
    246 #		if qdisc is None:
    247 #                        raise KeyError()
    248 #		else:
    249 #                        return Qdisc._from_capi(capi.qdisc2obj(qdisc))
    250 
    251     @staticmethod
    252     def _new_object(obj):
    253         return Qdisc(obj)
    254 
    255     @staticmethod
    256     def _new_cache(cache):
    257         return QdiscCache(cache=cache)
    258 
    259 class Qdisc(Tc):
    260     """Queueing discipline"""
    261 
    262     def __init__(self, obj=None):
    263         netlink.Object.__init__(self, 'route/qdisc', 'qdisc', obj)
    264         self._module_path = 'netlink.route.qdisc.'
    265         self._rtnl_qdisc = self._obj2type(self._nl_object)
    266         self._rtnl_tc = capi.obj2tc(self._nl_object)
    267 
    268         if self.kind:
    269             self._tc_module_lookup()
    270 
    271     @classmethod
    272     def from_capi(cls, obj):
    273         return cls(capi.qdisc2obj(obj))
    274 
    275     @staticmethod
    276     def _obj2type(obj):
    277         return capi.obj2qdisc(obj)
    278 
    279     @staticmethod
    280     def _new_instance(obj):
    281         if not obj:
    282             raise ValueError()
    283 
    284         return Qdisc(obj)
    285 
    286     @property
    287     def childs(self):
    288         ret = []
    289 
    290         if int(self.handle):
    291             ret += get_cls(self.ifindex, parent=self.handle)
    292 
    293             if self.root:
    294                 ret += get_class(self.ifindex, parent=TC_H_ROOT)
    295 
    296             ret += get_class(self.ifindex, parent=self.handle)
    297 
    298         return ret
    299 
    300 #	def add(self, socket, flags=None):
    301 #        	if not flags:
    302 #                        flags = netlink.NLM_F_CREATE
    303 #
    304 #		ret = capi.rtnl_link_add(socket._sock, self._link, flags)
    305 #		if ret < 0:
    306 #			raise netlink.KernelError(ret)
    307 #
    308 #	def change(self, socket, flags=0):
    309 #		"""Commit changes made to the link object"""
    310 #		if not self._orig:
    311 #			raise NetlinkError('Original link not available')
    312 #        	ret = capi.rtnl_link_change(socket._sock, self._orig, self._link, flags)
    313 #                if ret < 0:
    314 #                        raise netlink.KernelError(ret)
    315 #
    316 #	def delete(self, socket):
    317 #		"""Attempt to delete this link in the kernel"""
    318 #        	ret = capi.rtnl_link_delete(socket._sock, self._link)
    319 #                if ret < 0:
    320 #                        raise netlink.KernelError(ret)
    321 
    322     def format(self, details=False, stats=False, nodev=False,
    323            noparent=False, indent=''):
    324         """Return qdisc as formatted text"""
    325         fmt = util.MyFormatter(self, indent)
    326 
    327         buf = fmt.format(self.brief('qdisc', nodev, noparent))
    328 
    329         if details:
    330             buf += fmt.nl('\t' + self.details())
    331 
    332         if stats:
    333             buf += self.stats(fmt)
    334 
    335 #		if stats:
    336 #			l = [['Packets', RX_PACKETS, TX_PACKETS],
    337 #			     ['Bytes', RX_BYTES, TX_BYTES],
    338 #			     ['Errors', RX_ERRORS, TX_ERRORS],
    339 #			     ['Dropped', RX_DROPPED, TX_DROPPED],
    340 #			     ['Compressed', RX_COMPRESSED, TX_COMPRESSED],
    341 #			     ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR],
    342 #			     ['Length Errors', RX_LEN_ERR, None],
    343 #			     ['Over Errors', RX_OVER_ERR, None],
    344 #			     ['CRC Errors', RX_CRC_ERR, None],
    345 #			     ['Frame Errors', RX_FRAME_ERR, None],
    346 #			     ['Missed Errors', RX_MISSED_ERR, None],
    347 #			     ['Abort Errors', None, TX_ABORT_ERR],
    348 #			     ['Carrier Errors', None, TX_CARRIER_ERR],
    349 #			     ['Heartbeat Errors', None, TX_HBEAT_ERR],
    350 #			     ['Window Errors', None, TX_WIN_ERR],
    351 #			     ['Collisions', None, COLLISIONS],
    352 #			     ['Multicast', None, MULTICAST],
    353 #			     ['', None, None],
    354 #			     ['Ipv6:', None, None],
    355 #			     ['Packets', IP6_INPKTS, IP6_OUTPKTS],
    356 #			     ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS],
    357 #			     ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS],
    358 #			     ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS],
    359 #			     ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS],
    360 #			     ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS],
    361 #			     ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS],
    362 #			     ['Delivers', IP6_INDELIVERS, None],
    363 #			     ['Forwarded', None, IP6_OUTFORWDATAGRAMS],
    364 #			     ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES],
    365 #			     ['Header Errors', IP6_INHDRERRORS, None],
    366 #			     ['Too Big Errors', IP6_INTOOBIGERRORS, None],
    367 #			     ['Address Errors', IP6_INADDRERRORS, None],
    368 #			     ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None],
    369 #			     ['Truncated Packets', IP6_INTRUNCATEDPKTS, None],
    370 #			     ['Reasm Timeouts', IP6_REASMTIMEOUT, None],
    371 #			     ['Reasm Requests', IP6_REASMREQDS, None],
    372 #			     ['Reasm Failures', IP6_REASMFAILS, None],
    373 #			     ['Reasm OK', IP6_REASMOKS, None],
    374 #			     ['Frag Created', None, IP6_FRAGCREATES],
    375 #			     ['Frag Failures', None, IP6_FRAGFAILS],
    376 #			     ['Frag OK', None, IP6_FRAGOKS],
    377 #			     ['', None, None],
    378 #			     ['ICMPv6:', None, None],
    379 #			     ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS],
    380 #			     ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]]
    381 #
    382 #			buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'),
    383 #                        			   15 * ' ', util.title('TX'))
    384 #
    385 #			for row in l:
    386 #				row[0] = util.kw(row[0])
    387 #                                row[1] = self.get_stat(row[1]) if row[1] else ''
    388 #                                row[2] = self.get_stat(row[2]) if row[2] else ''
    389 #				buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row)
    390 
    391         return buf
    392 
    393 class TcClassCache(netlink.Cache):
    394     """Cache of traffic classes"""
    395 
    396     def __init__(self, ifindex, cache=None):
    397         if not cache:
    398             cache = self._alloc_cache_name('route/class')
    399 
    400         self._protocol = netlink.NETLINK_ROUTE
    401         self._nl_cache = cache
    402         self._set_arg1(ifindex)
    403 
    404     @staticmethod
    405     def _new_object(obj):
    406         return TcClass(obj)
    407 
    408     def _new_cache(self, cache):
    409         return TcClassCache(self.arg1, cache=cache)
    410 
    411 class TcClass(Tc):
    412     """Traffic Class"""
    413 
    414     def __init__(self, obj=None):
    415         netlink.Object.__init__(self, 'route/class', 'class', obj)
    416         self._module_path = 'netlink.route.qdisc.'
    417         self._rtnl_class = self._obj2type(self._nl_object)
    418         self._rtnl_tc = capi.obj2tc(self._nl_object)
    419 
    420         if self.kind:
    421             self._tc_module_lookup()
    422 
    423     @classmethod
    424     def from_capi(cls, obj):
    425         return cls(capi.class2obj(obj))
    426 
    427     @staticmethod
    428     def _obj2type(obj):
    429         return capi.obj2class(obj)
    430 
    431     @staticmethod
    432     def _new_instance(obj):
    433         if not obj:
    434             raise ValueError()
    435 
    436         return TcClass(obj)
    437 
    438     @property
    439     def childs(self):
    440         ret = []
    441 
    442         # classes can have classifiers, child classes and leaf
    443         # qdiscs
    444         ret += get_cls(self.ifindex, parent=self.handle)
    445         ret += get_class(self.ifindex, parent=self.handle)
    446         ret += get_qdisc(self.ifindex, parent=self.handle)
    447 
    448         return ret
    449 
    450     def format(self, details=False, _stats=False, nodev=False,
    451            noparent=False, indent=''):
    452         """Return class as formatted text"""
    453         fmt = util.MyFormatter(self, indent)
    454 
    455         buf = fmt.format(self.brief('class', nodev, noparent))
    456 
    457         if details:
    458             buf += fmt.nl('\t' + self.details())
    459 
    460         return buf
    461 
    462 class ClassifierCache(netlink.Cache):
    463     """Cache of traffic classifiers objects"""
    464 
    465     def __init__(self, ifindex, parent, cache=None):
    466         if not cache:
    467             cache = self._alloc_cache_name('route/cls')
    468 
    469         self._protocol = netlink.NETLINK_ROUTE
    470         self._nl_cache = cache
    471         self._set_arg1(ifindex)
    472         self._set_arg2(int(parent))
    473 
    474     @staticmethod
    475     def _new_object(obj):
    476         return Classifier(obj)
    477 
    478     def _new_cache(self, cache):
    479         return ClassifierCache(self.arg1, self.arg2, cache=cache)
    480 
    481 class Classifier(Tc):
    482     """Classifier"""
    483 
    484     def __init__(self, obj=None):
    485         netlink.Object.__init__(self, 'route/cls', 'cls', obj)
    486         self._module_path = 'netlink.route.cls.'
    487         self._rtnl_cls = self._obj2type(self._nl_object)
    488         self._rtnl_tc = capi.obj2tc(self._nl_object)
    489 
    490     @classmethod
    491     def from_capi(cls, obj):
    492         return cls(capi.cls2obj(obj))
    493 
    494     @staticmethod
    495     def _obj2type(obj):
    496         return capi.obj2cls(obj)
    497 
    498     @staticmethod
    499     def _new_instance(obj):
    500         if not obj:
    501             raise ValueError()
    502 
    503         return Classifier(obj)
    504 
    505     @property
    506     def priority(self):
    507         return capi.rtnl_cls_get_prio(self._rtnl_cls)
    508 
    509     @priority.setter
    510     def priority(self, value):
    511         capi.rtnl_cls_set_prio(self._rtnl_cls, int(value))
    512 
    513     @property
    514     def protocol(self):
    515         return capi.rtnl_cls_get_protocol(self._rtnl_cls)
    516 
    517     @protocol.setter
    518     def protocol(self, value):
    519         capi.rtnl_cls_set_protocol(self._rtnl_cls, int(value))
    520 
    521     @property
    522     def childs(self):
    523         return []
    524 
    525     def format(self, details=False, _stats=False, nodev=False,
    526            noparent=False, indent=''):
    527         """Return class as formatted text"""
    528         fmt = util.MyFormatter(self, indent)
    529 
    530         buf = fmt.format(self.brief('classifier', nodev, noparent))
    531         buf += fmt.format(' {t|priority} {t|protocol}')
    532 
    533         if details:
    534             buf += fmt.nl('\t' + self.details())
    535 
    536         return buf
    537 
    538 _qdisc_cache = QdiscCache()
    539 
    540 def get_qdisc(ifindex, handle=None, parent=None):
    541     l = []
    542 
    543     _qdisc_cache.refill()
    544 
    545     for qdisc in _qdisc_cache:
    546         if qdisc.ifindex != ifindex:
    547             continue
    548         if (handle is not None) and (qdisc.handle != handle):
    549             continue
    550         if (parent is not None) and (qdisc.parent != parent):
    551             continue
    552         l.append(qdisc)
    553 
    554     return l
    555 
    556 _class_cache = {}
    557 
    558 def get_class(ifindex, parent, handle=None):
    559     l = []
    560 
    561     try:
    562         cache = _class_cache[ifindex]
    563     except KeyError:
    564         cache = TcClassCache(ifindex)
    565         _class_cache[ifindex] = cache
    566 
    567     cache.refill()
    568 
    569     for cl in cache:
    570         if (parent is not None) and (cl.parent != parent):
    571             continue
    572         if (handle is not None) and (cl.handle != handle):
    573             continue
    574         l.append(cl)
    575 
    576     return l
    577 
    578 _cls_cache = {}
    579 
    580 def get_cls(ifindex, parent, handle=None):
    581 
    582     chain = _cls_cache.get(ifindex, dict())
    583 
    584     try:
    585         cache = chain[parent]
    586     except KeyError:
    587         cache = ClassifierCache(ifindex, parent)
    588         chain[parent] = cache
    589 
    590     cache.refill()
    591 
    592     if handle is None:
    593         return [ cls for cls in cache ]
    594 
    595     return [ cls for cls in cache if cls.handle == handle ]
    596