blob: 0689b71d0fc357c8598e37e4450082869552c108 [file] [log] [blame]
yuezonghe824eb0c2024-06-27 02:32:26 -07001#
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 ]