# # Copyright (c) 2011 Thomas Graf <tgraf@suug.ch> # from __future__ import absolute_import __all__ = [ 'TcCache', 'Tc', 'QdiscCache', 'Qdisc', 'TcClassCache', 'TcClass', ] from .. import core as netlink from . import capi as capi from .. import util as util from . import link as Link TC_PACKETS = 0 TC_BYTES = 1 TC_RATE_BPS = 2 TC_RATE_PPS = 3 TC_QLEN = 4 TC_BACKLOG = 5 TC_DROPS = 6 TC_REQUEUES = 7 TC_OVERLIMITS = 9 TC_H_ROOT = 0xFFFFFFFF TC_H_INGRESS = 0xFFFFFFF1 STAT_PACKETS = 0 STAT_BYTES = 1 STAT_RATE_BPS = 2 STAT_RATE_PPS = 3 STAT_QLEN = 4 STAT_BACKLOG = 5 STAT_DROPS = 6 STAT_REQUEUES = 7 STAT_OVERLIMITS = 8 STAT_MAX = STAT_OVERLIMITS class Handle(object): """ Traffic control handle Representation of a traffic control handle which uniquely identifies each traffic control object in its link namespace. handle = tc.Handle('10:20') handle = tc.handle('root') print int(handle) print str(handle) """ def __init__(self, val=None): if type(val) is str: val = capi.tc_str2handle(val) elif not val: val = 0 self._val = int(val) def __cmp__(self, other): if other is None: other = 0 if isinstance(other, Handle): return int(self) - int(other) elif isinstance(other, int): return int(self) - other else: raise TypeError() def __int__(self): return self._val def __str__(self): return capi.rtnl_tc_handle2str(self._val, 64)[0] def isroot(self): return self._val == TC_H_ROOT or self._val == TC_H_INGRESS class TcCache(netlink.Cache): """Cache of traffic control object""" def __getitem__(self, key): raise NotImplementedError() class Tc(netlink.Object): def __cmp__(self, other): diff = self.ifindex - other.ifindex if diff == 0: diff = int(self.handle) - int(other.handle) return diff def _tc_module_lookup(self): self._module_lookup(self._module_path + self.kind, 'init_' + self._name) @property def root(self): """True if tc object is a root object""" return self.parent.isroot() @property def ifindex(self): """interface index""" return capi.rtnl_tc_get_ifindex(self._rtnl_tc) @ifindex.setter def ifindex(self, value): capi.rtnl_tc_set_ifindex(self._rtnl_tc, int(value)) @property def link(self): link = capi.rtnl_tc_get_link(self._rtnl_tc) if not link: return None return Link.Link.from_capi(link) @link.setter def link(self, value): capi.rtnl_tc_set_link(self._rtnl_tc, value._link) @property def mtu(self): return capi.rtnl_tc_get_mtu(self._rtnl_tc) @mtu.setter def mtu(self, value): capi.rtnl_tc_set_mtu(self._rtnl_tc, int(value)) @property def mpu(self): return capi.rtnl_tc_get_mpu(self._rtnl_tc) @mpu.setter def mpu(self, value): capi.rtnl_tc_set_mpu(self._rtnl_tc, int(value)) @property def overhead(self): return capi.rtnl_tc_get_overhead(self._rtnl_tc) @overhead.setter def overhead(self, value): capi.rtnl_tc_set_overhead(self._rtnl_tc, int(value)) @property def linktype(self): return capi.rtnl_tc_get_linktype(self._rtnl_tc) @linktype.setter def linktype(self, value): capi.rtnl_tc_set_linktype(self._rtnl_tc, int(value)) @property @netlink.nlattr(fmt=util.handle) def handle(self): return Handle(capi.rtnl_tc_get_handle(self._rtnl_tc)) @handle.setter def handle(self, value): capi.rtnl_tc_set_handle(self._rtnl_tc, int(value)) @property @netlink.nlattr(fmt=util.handle) def parent(self): return Handle(capi.rtnl_tc_get_parent(self._rtnl_tc)) .setter def parent(self, value): capi.rtnl_tc_set_parent(self._rtnl_tc, int(value)) @property @netlink.nlattr(fmt=util.bold) def kind(self): return capi.rtnl_tc_get_kind(self._rtnl_tc) @kind.setter def kind(self, value): capi.rtnl_tc_set_kind(self._rtnl_tc, value) self._tc_module_lookup() def get_stat(self, id): return capi.rtnl_tc_get_stat(self._rtnl_tc, id) @property def _dev(self): buf = util.kw('dev') + ' ' if self.link: return buf + util.string(self.link.name) else: return buf + util.num(self.ifindex) def brief(self, title, nodev=False, noparent=False): ret = title + ' {a|kind} {a|handle}' if not nodev: ret += ' {a|_dev}' if not noparent: ret += ' {t|parent}' return ret + self._module_brief() @staticmethod def details(): return '{t|mtu} {t|mpu} {t|overhead} {t|linktype}' @property def packets(self): return self.get_stat(STAT_PACKETS) @property def bytes(self): return self.get_stat(STAT_BYTES) @property def qlen(self): return self.get_stat(STAT_QLEN) @staticmethod def stats(fmt): return fmt.nl('{t|packets} {t|bytes} {t|qlen}') class QdiscCache(netlink.Cache): """Cache of qdiscs""" def __init__(self, cache=None): if not cache: cache = self._alloc_cache_name('route/qdisc') self._protocol = netlink.NETLINK_ROUTE self._nl_cache = cache # def __getitem__(self, key): # if type(key) is int: # link = capi.rtnl_link_get(self._this, key) # elif type(key) is str: # link = capi.rtnl_link_get_by_name(self._this, key) # # if qdisc is None: # raise KeyError() # else: # return Qdisc._from_capi(capi.qdisc2obj(qdisc)) @staticmethod def _new_object(obj): return Qdisc(obj) @staticmethod def _new_cache(cache): return QdiscCache(cache=cache) class Qdisc(Tc): """Queueing discipline""" def __init__(self, obj=None): netlink.Object.__init__(self, 'route/qdisc', 'qdisc', obj) self._module_path = 'netlink.route.qdisc.' self._rtnl_qdisc = self._obj2type(self._nl_object) self._rtnl_tc = capi.obj2tc(self._nl_object) if self.kind: self._tc_module_lookup() @classmethod def from_capi(cls, obj): return cls(capi.qdisc2obj(obj)) @staticmethod def _obj2type(obj): return capi.obj2qdisc(obj) @staticmethod def _new_instance(obj): if not obj: raise ValueError() return Qdisc(obj) @property def childs(self): ret = [] if int(self.handle): ret += get_cls(self.ifindex, parent=self.handle) if self.root: ret += get_class(self.ifindex, parent=TC_H_ROOT) ret += get_class(self.ifindex, parent=self.handle) return ret # def add(self, socket, flags=None): # if not flags: # flags = netlink.NLM_F_CREATE # # ret = capi.rtnl_link_add(socket._sock, self._link, flags) # if ret < 0: # raise netlink.KernelError(ret) # # def change(self, socket, flags=0): # """Commit changes made to the link object""" # if not self._orig: # raise NetlinkError('Original link not available') # ret = capi.rtnl_link_change(socket._sock, self._orig, self._link, flags) # if ret < 0: # raise netlink.KernelError(ret) # # def delete(self, socket): # """Attempt to delete this link in the kernel""" # ret = capi.rtnl_link_delete(socket._sock, self._link) # if ret < 0: # raise netlink.KernelError(ret) def format(self, details=False, stats=False, nodev=False, noparent=False, indent=''): """Return qdisc as formatted text""" fmt = util.MyFormatter(self, indent) buf = fmt.format(self.brief('qdisc', nodev, noparent)) if details: buf += fmt.nl('\t' + self.details()) if stats: buf += self.stats(fmt) # if stats: # l = [['Packets', RX_PACKETS, TX_PACKETS], # ['Bytes', RX_BYTES, TX_BYTES], # ['Errors', RX_ERRORS, TX_ERRORS], # ['Dropped', RX_DROPPED, TX_DROPPED], # ['Compressed', RX_COMPRESSED, TX_COMPRESSED], # ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR], # ['Length Errors', RX_LEN_ERR, None], # ['Over Errors', RX_OVER_ERR, None], # ['CRC Errors', RX_CRC_ERR, None], # ['Frame Errors', RX_FRAME_ERR, None], # ['Missed Errors', RX_MISSED_ERR, None], # ['Abort Errors', None, TX_ABORT_ERR], # ['Carrier Errors', None, TX_CARRIER_ERR], # ['Heartbeat Errors', None, TX_HBEAT_ERR], # ['Window Errors', None, TX_WIN_ERR], # ['Collisions', None, COLLISIONS], # ['Multicast', None, MULTICAST], # ['', None, None], # ['Ipv6:', None, None], # ['Packets', IP6_INPKTS, IP6_OUTPKTS], # ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS], # ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS], # ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS], # ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS], # ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS], # ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS], # ['Delivers', IP6_INDELIVERS, None], # ['Forwarded', None, IP6_OUTFORWDATAGRAMS], # ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES], # ['Header Errors', IP6_INHDRERRORS, None], # ['Too Big Errors', IP6_INTOOBIGERRORS, None], # ['Address Errors', IP6_INADDRERRORS, None], # ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None], # ['Truncated Packets', IP6_INTRUNCATEDPKTS, None], # ['Reasm Timeouts', IP6_REASMTIMEOUT, None], # ['Reasm Requests', IP6_REASMREQDS, None], # ['Reasm Failures', IP6_REASMFAILS, None], # ['Reasm OK', IP6_REASMOKS, None], # ['Frag Created', None, IP6_FRAGCREATES], # ['Frag Failures', None, IP6_FRAGFAILS], # ['Frag OK', None, IP6_FRAGOKS], # ['', None, None], # ['ICMPv6:', None, None], # ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS], # ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]] # # buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'), # 15 * ' ', util.title('TX')) # # for row in l: # row[0] = util.kw(row[0]) # row[1] = self.get_stat(row[1]) if row[1] else '' # row[2] = self.get_stat(row[2]) if row[2] else '' # buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row) return buf class TcClassCache(netlink.Cache): """Cache of traffic classes""" def __init__(self, ifindex, cache=None): if not cache: cache = self._alloc_cache_name('route/class') self._protocol = netlink.NETLINK_ROUTE self._nl_cache = cache self._set_arg1(ifindex) @staticmethod def _new_object(obj): return TcClass(obj) def _new_cache(self, cache): return TcClassCache(self.arg1, cache=cache) class TcClass(Tc): """Traffic Class""" def __init__(self, obj=None): netlink.Object.__init__(self, 'route/class', 'class', obj) self._module_path = 'netlink.route.qdisc.' self._rtnl_class = self._obj2type(self._nl_object) self._rtnl_tc = capi.obj2tc(self._nl_object) if self.kind: self._tc_module_lookup() @classmethod def from_capi(cls, obj): return cls(capi.class2obj(obj)) @staticmethod def _obj2type(obj): return capi.obj2class(obj) @staticmethod def _new_instance(obj): if not obj: raise ValueError() return TcClass(obj) @property def childs(self): ret = [] # classes can have classifiers, child classes and leaf # qdiscs ret += get_cls(self.ifindex, parent=self.handle) ret += get_class(self.ifindex, parent=self.handle) ret += get_qdisc(self.ifindex, parent=self.handle) return ret def format(self, details=False, _stats=False, nodev=False, noparent=False, indent=''): """Return class as formatted text""" fmt = util.MyFormatter(self, indent) buf = fmt.format(self.brief('class', nodev, noparent)) if details: buf += fmt.nl('\t' + self.details()) return buf class ClassifierCache(netlink.Cache): """Cache of traffic classifiers objects""" def __init__(self, ifindex, parent, cache=None): if not cache: cache = self._alloc_cache_name('route/cls') self._protocol = netlink.NETLINK_ROUTE self._nl_cache = cache self._set_arg1(ifindex) self._set_arg2(int(parent)) @staticmethod def _new_object(obj): return Classifier(obj) def _new_cache(self, cache): return ClassifierCache(self.arg1, self.arg2, cache=cache) class Classifier(Tc): """Classifier""" def __init__(self, obj=None): netlink.Object.__init__(self, 'route/cls', 'cls', obj) self._module_path = 'netlink.route.cls.' self._rtnl_cls = self._obj2type(self._nl_object) self._rtnl_tc = capi.obj2tc(self._nl_object) @classmethod def from_capi(cls, obj): return cls(capi.cls2obj(obj)) @staticmethod def _obj2type(obj): return capi.obj2cls(obj) @staticmethod def _new_instance(obj): if not obj: raise ValueError() return Classifier(obj) @property def priority(self): return capi.rtnl_cls_get_prio(self._rtnl_cls) @priority.setter def priority(self, value): capi.rtnl_cls_set_prio(self._rtnl_cls, int(value)) @property def protocol(self): return capi.rtnl_cls_get_protocol(self._rtnl_cls) @protocol.setter def protocol(self, value): capi.rtnl_cls_set_protocol(self._rtnl_cls, int(value)) @property def childs(self): return [] def format(self, details=False, _stats=False, nodev=False, noparent=False, indent=''): """Return class as formatted text""" fmt = util.MyFormatter(self, indent) buf = fmt.format(self.brief('classifier', nodev, noparent)) buf += fmt.format(' {t|priority} {t|protocol}') if details: buf += fmt.nl('\t' + self.details()) return buf _qdisc_cache = QdiscCache() def get_qdisc(ifindex, handle=None, parent=None): l = [] _qdisc_cache.refill() for qdisc in _qdisc_cache: if qdisc.ifindex != ifindex: continue if (handle is not None) and (qdisc.handle != handle): continue if (parent is not None) and (qdisc.parent != parent): continue l.append(qdisc) return l _class_cache = {} def get_class(ifindex, parent, handle=None): l = [] try: cache = _class_cache[ifindex] except KeyError: cache = TcClassCache(ifindex) _class_cache[ifindex] = cache cache.refill() for cl in cache: if (parent is not None) and (cl.parent != parent): continue if (handle is not None) and (cl.handle != handle): continue l.append(cl) return l _cls_cache = {} def get_cls(ifindex, parent, handle=None): chain = _cls_cache.get(ifindex, dict()) try: cache = chain[parent] except KeyError: cache = ClassifierCache(ifindex, parent) chain[parent] = cache cache.refill() if handle is None: return [ cls for cls in cache ] return [ cls for cls in cache if cls.handle == handle ]