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