1# 2# Copyright (c) 2011 Thomas Graf <[email protected]> 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 57 def __init__(self, val=None): 58 if type(val) is str: 59 val = capi.tc_str2handle(val) 60 elif not val: 61 val = 0 62 63 self._val = int(val) 64 65 def __cmp__(self, other): 66 if other is None: 67 other = 0 68 69 if isinstance(other, Handle): 70 return int(self) - int(other) 71 elif isinstance(other, int): 72 return int(self) - other 73 else: 74 raise TypeError() 75 76 def __int__(self): 77 return self._val 78 79 def __str__(self): 80 return capi.rtnl_tc_handle2str(self._val, 64)[0] 81 82 def isroot(self): 83 return self._val == TC_H_ROOT or self._val == TC_H_INGRESS 84 85 86class TcCache(netlink.Cache): 87 """Cache of traffic control object""" 88 89 def __getitem__(self, key): 90 raise NotImplementedError() 91 92 93class Tc(netlink.Object): 94 def __cmp__(self, other): 95 diff = self.ifindex - other.ifindex 96 if diff == 0: 97 diff = int(self.handle) - int(other.handle) 98 return diff 99 100 def _tc_module_lookup(self): 101 self._module_lookup(self._module_path + self.kind, "init_" + self._name) 102 103 @property 104 def root(self): 105 """True if tc object is a root object""" 106 return self.parent.isroot() 107 108 @property 109 def ifindex(self): 110 """interface index""" 111 return capi.rtnl_tc_get_ifindex(self._rtnl_tc) 112 113 @ifindex.setter 114 def ifindex(self, value): 115 capi.rtnl_tc_set_ifindex(self._rtnl_tc, int(value)) 116 117 @property 118 def link(self): 119 link = capi.rtnl_tc_get_link(self._rtnl_tc) 120 if not link: 121 return None 122 123 return Link.Link.from_capi(link) 124 125 @link.setter 126 def link(self, value): 127 capi.rtnl_tc_set_link(self._rtnl_tc, value._link) 128 129 @property 130 def mtu(self): 131 return capi.rtnl_tc_get_mtu(self._rtnl_tc) 132 133 @mtu.setter 134 def mtu(self, value): 135 capi.rtnl_tc_set_mtu(self._rtnl_tc, int(value)) 136 137 @property 138 def mpu(self): 139 return capi.rtnl_tc_get_mpu(self._rtnl_tc) 140 141 @mpu.setter 142 def mpu(self, value): 143 capi.rtnl_tc_set_mpu(self._rtnl_tc, int(value)) 144 145 @property 146 def overhead(self): 147 return capi.rtnl_tc_get_overhead(self._rtnl_tc) 148 149 @overhead.setter 150 def overhead(self, value): 151 capi.rtnl_tc_set_overhead(self._rtnl_tc, int(value)) 152 153 @property 154 def linktype(self): 155 return capi.rtnl_tc_get_linktype(self._rtnl_tc) 156 157 @linktype.setter 158 def linktype(self, value): 159 capi.rtnl_tc_set_linktype(self._rtnl_tc, int(value)) 160 161 @property 162 @netlink.nlattr(fmt=util.handle) 163 def handle(self): 164 return Handle(capi.rtnl_tc_get_handle(self._rtnl_tc)) 165 166 @handle.setter 167 def handle(self, value): 168 capi.rtnl_tc_set_handle(self._rtnl_tc, int(value)) 169 170 @property 171 @netlink.nlattr(fmt=util.handle) 172 def parent(self): 173 return Handle(capi.rtnl_tc_get_parent(self._rtnl_tc)) 174 175 @parent.setter 176 def parent(self, value): 177 capi.rtnl_tc_set_parent(self._rtnl_tc, int(value)) 178 179 @property 180 @netlink.nlattr(fmt=util.bold) 181 def kind(self): 182 return capi.rtnl_tc_get_kind(self._rtnl_tc) 183 184 @kind.setter 185 def kind(self, value): 186 capi.rtnl_tc_set_kind(self._rtnl_tc, value) 187 self._tc_module_lookup() 188 189 def get_stat(self, id): 190 return capi.rtnl_tc_get_stat(self._rtnl_tc, id) 191 192 @property 193 def _dev(self): 194 buf = util.kw("dev") + " " 195 196 if self.link: 197 return buf + util.string(self.link.name) 198 else: 199 return buf + util.num(self.ifindex) 200 201 def brief(self, title, nodev=False, noparent=False): 202 ret = title + " {a|kind} {a|handle}" 203 204 if not nodev: 205 ret += " {a|_dev}" 206 207 if not noparent: 208 ret += " {t|parent}" 209 210 return ret + self._module_brief() 211 212 @staticmethod 213 def details(): 214 return "{t|mtu} {t|mpu} {t|overhead} {t|linktype}" 215 216 @property 217 def packets(self): 218 return self.get_stat(STAT_PACKETS) 219 220 @property 221 def bytes(self): 222 return self.get_stat(STAT_BYTES) 223 224 @property 225 def qlen(self): 226 return self.get_stat(STAT_QLEN) 227 228 @staticmethod 229 def stats(fmt): 230 return fmt.nl("{t|packets} {t|bytes} {t|qlen}") 231 232 233class QdiscCache(netlink.Cache): 234 """Cache of qdiscs""" 235 236 def __init__(self, cache=None): 237 if not cache: 238 cache = self._alloc_cache_name("route/qdisc") 239 240 self._protocol = netlink.NETLINK_ROUTE 241 self._nl_cache = cache 242 243 # def __getitem__(self, key): 244 # if type(key) is int: 245 # link = capi.rtnl_link_get(self._this, key) 246 # elif type(key) is str: 247 # link = capi.rtnl_link_get_by_name(self._this, key) 248 # 249 # if qdisc is None: 250 # raise KeyError() 251 # else: 252 # return Qdisc._from_capi(capi.qdisc2obj(qdisc)) 253 254 @staticmethod 255 def _new_object(obj): 256 return Qdisc(obj) 257 258 @staticmethod 259 def _new_cache(cache): 260 return QdiscCache(cache=cache) 261 262 263class Qdisc(Tc): 264 """Queueing discipline""" 265 266 def __init__(self, obj=None): 267 netlink.Object.__init__(self, "route/qdisc", "qdisc", obj) 268 self._module_path = "netlink.route.qdisc." 269 self._rtnl_qdisc = self._obj2type(self._nl_object) 270 self._rtnl_tc = capi.obj2tc(self._nl_object) 271 272 if self.kind: 273 self._tc_module_lookup() 274 275 @classmethod 276 def from_capi(cls, obj): 277 return cls(capi.qdisc2obj(obj)) 278 279 @staticmethod 280 def _obj2type(obj): 281 return capi.obj2qdisc(obj) 282 283 @staticmethod 284 def _new_instance(obj): 285 if not obj: 286 raise ValueError() 287 288 return Qdisc(obj) 289 290 @property 291 def childs(self): 292 ret = [] 293 294 if int(self.handle): 295 ret += get_cls(self.ifindex, parent=self.handle) 296 297 if self.root: 298 ret += get_class(self.ifindex, parent=TC_H_ROOT) 299 300 ret += get_class(self.ifindex, parent=self.handle) 301 302 return ret 303 304 # def add(self, socket, flags=None): 305 # if not flags: 306 # flags = netlink.NLM_F_CREATE 307 # 308 # ret = capi.rtnl_link_add(socket._sock, self._link, flags) 309 # if ret < 0: 310 # raise netlink.KernelError(ret) 311 # 312 # def change(self, socket, flags=0): 313 # """Commit changes made to the link object""" 314 # if not self._orig: 315 # raise NetlinkError('Original link not available') 316 # ret = capi.rtnl_link_change(socket._sock, self._orig, self._link, flags) 317 # if ret < 0: 318 # raise netlink.KernelError(ret) 319 # 320 # def delete(self, socket): 321 # """Attempt to delete this link in the kernel""" 322 # ret = capi.rtnl_link_delete(socket._sock, self._link) 323 # if ret < 0: 324 # raise netlink.KernelError(ret) 325 326 def format( 327 self, details=False, stats=False, nodev=False, noparent=False, indent="" 328 ): 329 """Return qdisc as formatted text""" 330 fmt = util.MyFormatter(self, indent) 331 332 buf = fmt.format(self.brief("qdisc", nodev, noparent)) 333 334 if details: 335 buf += fmt.nl("\t" + self.details()) 336 337 if stats: 338 buf += self.stats(fmt) 339 340 # if stats: 341 # l = [['Packets', RX_PACKETS, TX_PACKETS], 342 # ['Bytes', RX_BYTES, TX_BYTES], 343 # ['Errors', RX_ERRORS, TX_ERRORS], 344 # ['Dropped', RX_DROPPED, TX_DROPPED], 345 # ['Compressed', RX_COMPRESSED, TX_COMPRESSED], 346 # ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR], 347 # ['Length Errors', RX_LEN_ERR, None], 348 # ['Over Errors', RX_OVER_ERR, None], 349 # ['CRC Errors', RX_CRC_ERR, None], 350 # ['Frame Errors', RX_FRAME_ERR, None], 351 # ['Missed Errors', RX_MISSED_ERR, None], 352 # ['Abort Errors', None, TX_ABORT_ERR], 353 # ['Carrier Errors', None, TX_CARRIER_ERR], 354 # ['Heartbeat Errors', None, TX_HBEAT_ERR], 355 # ['Window Errors', None, TX_WIN_ERR], 356 # ['Collisions', None, COLLISIONS], 357 # ['Multicast', None, MULTICAST], 358 # ['', None, None], 359 # ['Ipv6:', None, None], 360 # ['Packets', IP6_INPKTS, IP6_OUTPKTS], 361 # ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS], 362 # ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS], 363 # ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS], 364 # ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS], 365 # ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS], 366 # ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS], 367 # ['Delivers', IP6_INDELIVERS, None], 368 # ['Forwarded', None, IP6_OUTFORWDATAGRAMS], 369 # ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES], 370 # ['Header Errors', IP6_INHDRERRORS, None], 371 # ['Too Big Errors', IP6_INTOOBIGERRORS, None], 372 # ['Address Errors', IP6_INADDRERRORS, None], 373 # ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None], 374 # ['Truncated Packets', IP6_INTRUNCATEDPKTS, None], 375 # ['Reasm Timeouts', IP6_REASMTIMEOUT, None], 376 # ['Reasm Requests', IP6_REASMREQDS, None], 377 # ['Reasm Failures', IP6_REASMFAILS, None], 378 # ['Reasm OK', IP6_REASMOKS, None], 379 # ['Frag Created', None, IP6_FRAGCREATES], 380 # ['Frag Failures', None, IP6_FRAGFAILS], 381 # ['Frag OK', None, IP6_FRAGOKS], 382 # ['', None, None], 383 # ['ICMPv6:', None, None], 384 # ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS], 385 # ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]] 386 # 387 # buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'), 388 # 15 * ' ', util.title('TX')) 389 # 390 # for row in l: 391 # row[0] = util.kw(row[0]) 392 # row[1] = self.get_stat(row[1]) if row[1] else '' 393 # row[2] = self.get_stat(row[2]) if row[2] else '' 394 # buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row) 395 396 return buf 397 398 399class TcClassCache(netlink.Cache): 400 """Cache of traffic classes""" 401 402 def __init__(self, ifindex, cache=None): 403 if not cache: 404 cache = self._alloc_cache_name("route/class") 405 406 self._protocol = netlink.NETLINK_ROUTE 407 self._nl_cache = cache 408 self._set_arg1(ifindex) 409 410 @staticmethod 411 def _new_object(obj): 412 return TcClass(obj) 413 414 def _new_cache(self, cache): 415 return TcClassCache(self.arg1, cache=cache) 416 417 418class TcClass(Tc): 419 """Traffic Class""" 420 421 def __init__(self, obj=None): 422 netlink.Object.__init__(self, "route/class", "class", obj) 423 self._module_path = "netlink.route.qdisc." 424 self._rtnl_class = self._obj2type(self._nl_object) 425 self._rtnl_tc = capi.obj2tc(self._nl_object) 426 427 if self.kind: 428 self._tc_module_lookup() 429 430 @classmethod 431 def from_capi(cls, obj): 432 return cls(capi.class2obj(obj)) 433 434 @staticmethod 435 def _obj2type(obj): 436 return capi.obj2class(obj) 437 438 @staticmethod 439 def _new_instance(obj): 440 if not obj: 441 raise ValueError() 442 443 return TcClass(obj) 444 445 @property 446 def childs(self): 447 ret = [] 448 449 # classes can have classifiers, child classes and leaf 450 # qdiscs 451 ret += get_cls(self.ifindex, parent=self.handle) 452 ret += get_class(self.ifindex, parent=self.handle) 453 ret += get_qdisc(self.ifindex, parent=self.handle) 454 455 return ret 456 457 def format( 458 self, details=False, _stats=False, nodev=False, noparent=False, indent="" 459 ): 460 """Return class as formatted text""" 461 fmt = util.MyFormatter(self, indent) 462 463 buf = fmt.format(self.brief("class", nodev, noparent)) 464 465 if details: 466 buf += fmt.nl("\t" + self.details()) 467 468 return buf 469 470 471class ClassifierCache(netlink.Cache): 472 """Cache of traffic classifiers objects""" 473 474 def __init__(self, ifindex, parent, cache=None): 475 if not cache: 476 cache = self._alloc_cache_name("route/cls") 477 478 self._protocol = netlink.NETLINK_ROUTE 479 self._nl_cache = cache 480 self._set_arg1(ifindex) 481 self._set_arg2(int(parent)) 482 483 @staticmethod 484 def _new_object(obj): 485 return Classifier(obj) 486 487 def _new_cache(self, cache): 488 return ClassifierCache(self.arg1, self.arg2, cache=cache) 489 490 491class Classifier(Tc): 492 """Classifier""" 493 494 def __init__(self, obj=None): 495 netlink.Object.__init__(self, "route/cls", "cls", obj) 496 self._module_path = "netlink.route.cls." 497 self._rtnl_cls = self._obj2type(self._nl_object) 498 self._rtnl_tc = capi.obj2tc(self._nl_object) 499 500 @classmethod 501 def from_capi(cls, obj): 502 return cls(capi.cls2obj(obj)) 503 504 @staticmethod 505 def _obj2type(obj): 506 return capi.obj2cls(obj) 507 508 @staticmethod 509 def _new_instance(obj): 510 if not obj: 511 raise ValueError() 512 513 return Classifier(obj) 514 515 @property 516 def priority(self): 517 return capi.rtnl_cls_get_prio(self._rtnl_cls) 518 519 @priority.setter 520 def priority(self, value): 521 capi.rtnl_cls_set_prio(self._rtnl_cls, int(value)) 522 523 @property 524 def protocol(self): 525 return capi.rtnl_cls_get_protocol(self._rtnl_cls) 526 527 @protocol.setter 528 def protocol(self, value): 529 capi.rtnl_cls_set_protocol(self._rtnl_cls, int(value)) 530 531 @property 532 def childs(self): 533 return [] 534 535 def format( 536 self, details=False, _stats=False, nodev=False, noparent=False, indent="" 537 ): 538 """Return class as formatted text""" 539 fmt = util.MyFormatter(self, indent) 540 541 buf = fmt.format(self.brief("classifier", nodev, noparent)) 542 buf += fmt.format(" {t|priority} {t|protocol}") 543 544 if details: 545 buf += fmt.nl("\t" + self.details()) 546 547 return buf 548 549 550_qdisc_cache = QdiscCache() 551 552 553def get_qdisc(ifindex, handle=None, parent=None): 554 lst = [] 555 556 _qdisc_cache.refill() 557 558 for qdisc in _qdisc_cache: 559 if qdisc.ifindex != ifindex: 560 continue 561 if (handle is not None) and (qdisc.handle != handle): 562 continue 563 if (parent is not None) and (qdisc.parent != parent): 564 continue 565 lst.append(qdisc) 566 567 return lst 568 569 570_class_cache = {} 571 572 573def get_class(ifindex, parent, handle=None): 574 lst = [] 575 576 try: 577 cache = _class_cache[ifindex] 578 except KeyError: 579 cache = TcClassCache(ifindex) 580 _class_cache[ifindex] = cache 581 582 cache.refill() 583 584 for cl in cache: 585 if (parent is not None) and (cl.parent != parent): 586 continue 587 if (handle is not None) and (cl.handle != handle): 588 continue 589 lst.append(cl) 590 591 return lst 592 593 594_cls_cache = {} 595 596 597def get_cls(ifindex, parent, handle=None): 598 599 chain = _cls_cache.get(ifindex, {}) 600 601 try: 602 cache = chain[parent] 603 except KeyError: 604 cache = ClassifierCache(ifindex, parent) 605 chain[parent] = cache 606 607 cache.refill() 608 609 if handle is None: 610 return list(cache) 611 612 return [cls for cls in cache if cls.handle == handle] 613