1#
2# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
3#
4from __future__ import absolute_import
5
6__all__ = [
7    'TcCache',
8    'Tc',
9    'QdiscCache',
10    'Qdisc',
11    'TcClassCache',
12    'TcClass',
13]
14
15from .. import core as netlink
16from .  import capi as capi
17from .. import util as util
18from .  import link as Link
19
20TC_PACKETS = 0
21TC_BYTES = 1
22TC_RATE_BPS = 2
23TC_RATE_PPS = 3
24TC_QLEN = 4
25TC_BACKLOG = 5
26TC_DROPS = 6
27TC_REQUEUES = 7
28TC_OVERLIMITS = 9
29
30TC_H_ROOT = 0xFFFFFFFF
31TC_H_INGRESS = 0xFFFFFFF1
32
33STAT_PACKETS = 0
34STAT_BYTES = 1
35STAT_RATE_BPS = 2
36STAT_RATE_PPS = 3
37STAT_QLEN = 4
38STAT_BACKLOG = 5
39STAT_DROPS = 6
40STAT_REQUEUES = 7
41STAT_OVERLIMITS = 8
42STAT_MAX = STAT_OVERLIMITS
43
44
45class 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
84class TcCache(netlink.Cache):
85    """Cache of traffic control object"""
86
87    def __getitem__(self, key):
88        raise NotImplementedError()
89
90class 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
230class 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
259class 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
393class 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
411class 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
462class 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
481class 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
540def 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
558def 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
580def 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