You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
596 lines
16 KiB
596 lines
16 KiB
#
|
|
# 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))
|
|
|
|
@parent.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 ]
|