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