1# 2# Netlink interface based on libnl 3# 4# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch> 5# 6 7"""netlink library based on libnl 8 9This module provides an interface to netlink sockets 10 11The module contains the following public classes: 12 - Socket -- The netlink socket 13 - Message -- The netlink message 14 - Callback -- The netlink callback handler 15 - Object -- Abstract object (based on struct nl_obect in libnl) used as 16 base class for all object types which can be put into a Cache 17 - Cache -- A collection of objects which are derived from the base 18 class Object. Used for netlink protocols which maintain a list 19 or tree of objects. 20 - DumpParams -- 21 22The following exceptions are defined: 23 - NetlinkError -- Base exception for all general purpose exceptions raised. 24 - KernelError -- Raised when the kernel returns an error as response to a 25 request. 26 27All other classes or functions in this module are considered implementation 28details. 29""" 30from __future__ import absolute_import 31 32 33 34from . import capi 35import sys 36import socket 37 38__all__ = [ 39 'Socket', 40 'Message', 41 'Callback', 42 'DumpParams', 43 'Object', 44 'Cache', 45 'KernelError', 46 'NetlinkError', 47] 48 49__version__ = '0.1' 50 51# netlink protocols 52NETLINK_ROUTE = 0 53# NETLINK_UNUSED = 1 54NETLINK_USERSOCK = 2 55NETLINK_FIREWALL = 3 56NETLINK_INET_DIAG = 4 57NETLINK_NFLOG = 5 58NETLINK_XFRM = 6 59NETLINK_SELINUX = 7 60NETLINK_ISCSI = 8 61NETLINK_AUDIT = 9 62NETLINK_FIB_LOOKUP = 10 63NETLINK_CONNECTOR = 11 64NETLINK_NETFILTER = 12 65NETLINK_IP6_FW = 13 66NETLINK_DNRTMSG = 14 67NETLINK_KOBJECT_UEVENT = 15 68NETLINK_GENERIC = 16 69NETLINK_SCSITRANSPORT = 18 70NETLINK_ECRYPTFS = 19 71 72NL_DONTPAD = 0 73NL_AUTO_PORT = 0 74NL_AUTO_SEQ = 0 75 76NL_DUMP_LINE = 0 77NL_DUMP_DETAILS = 1 78NL_DUMP_STATS = 2 79 80NLM_F_REQUEST = 1 81NLM_F_MULTI = 2 82NLM_F_ACK = 4 83NLM_F_ECHO = 8 84 85NLM_F_ROOT = 0x100 86NLM_F_MATCH = 0x200 87NLM_F_ATOMIC = 0x400 88NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH 89 90NLM_F_REPLACE = 0x100 91NLM_F_EXCL = 0x200 92NLM_F_CREATE = 0x400 93NLM_F_APPEND = 0x800 94 95class NetlinkError(Exception): 96 def __init__(self, error): 97 self._error = error 98 self._msg = capi.nl_geterror(error) 99 100 def __str__(self): 101 return self._msg 102 103class KernelError(NetlinkError): 104 def __str__(self): 105 return 'Kernel returned: {0}'.format(self._msg) 106 107class ImmutableError(NetlinkError): 108 def __init__(self, msg): 109 self._msg = msg 110 111 def __str__(self): 112 return 'Immutable attribute: {0}'.format(self._msg) 113 114class Message(object): 115 """Netlink message""" 116 117 def __init__(self, size=0): 118 if size == 0: 119 self._msg = capi.nlmsg_alloc() 120 else: 121 self._msg = capi.nlmsg_alloc_size(size) 122 123 if self._msg is None: 124 raise Exception('Message allocation returned NULL') 125 126 def __del__(self): 127 capi.nlmsg_free(self._msg) 128 129 def __len__(self): 130 return capi.nlmsg_len(nlmsg_hdr(self._msg)) 131 132 @property 133 def protocol(self): 134 return capi.nlmsg_get_proto(self._msg) 135 136 @protocol.setter 137 def protocol(self, value): 138 capi.nlmsg_set_proto(self._msg, value) 139 140 @property 141 def maxSize(self): 142 return capi.nlmsg_get_max_size(self._msg) 143 144 @property 145 def hdr(self): 146 return capi.nlmsg_hdr(self._msg) 147 148 @property 149 def data(self): 150 return capi.nlmsg_data(self._msg) 151 152 @property 153 def attrs(self): 154 return capi.nlmsg_attrdata(self._msg) 155 156 def send(self, sock): 157 sock.send(self) 158 159class Callback(object): 160 """Netlink callback""" 161 162 def __init__(self, kind=capi.NL_CB_DEFAULT): 163 if isinstance(kind, Callback): 164 self._cb = capi.py_nl_cb_clone(kind._cb) 165 else: 166 self._cb = capi.nl_cb_alloc(kind) 167 168 def __del__(self): 169 capi.py_nl_cb_put(self._cb) 170 171 def set_type(self, t, k, handler, obj): 172 return capi.py_nl_cb_set(self._cb, t, k, handler, obj) 173 174 def set_all(self, k, handler, obj): 175 return capi.py_nl_cb_set_all(self._cb, k, handler, obj) 176 177 def set_err(self, k, handler, obj): 178 return capi.py_nl_cb_err(self._cb, k, handler, obj) 179 180 def clone(self): 181 return Callback(self) 182 183class Socket(object): 184 """Netlink socket""" 185 186 def __init__(self, cb=None): 187 if isinstance(cb, Callback): 188 self._sock = capi.nl_socket_alloc_cb(cb._cb) 189 elif cb == None: 190 self._sock = capi.nl_socket_alloc() 191 else: 192 raise Exception('\'cb\' parameter has wrong type') 193 194 if self._sock is None: 195 raise Exception('NULL pointer returned while allocating socket') 196 197 def __del__(self): 198 capi.nl_socket_free(self._sock) 199 200 def __str__(self): 201 return 'nlsock<{0}>'.format(self.local_port) 202 203 @property 204 def local_port(self): 205 return capi.nl_socket_get_local_port(self._sock) 206 207 @local_port.setter 208 def local_port(self, value): 209 capi.nl_socket_set_local_port(self._sock, int(value)) 210 211 @property 212 def peer_port(self): 213 return capi.nl_socket_get_peer_port(self._sock) 214 215 @peer_port.setter 216 def peer_port(self, value): 217 capi.nl_socket_set_peer_port(self._sock, int(value)) 218 219 @property 220 def peer_groups(self): 221 return capi.nl_socket_get_peer_groups(self._sock) 222 223 @peer_groups.setter 224 def peer_groups(self, value): 225 capi.nl_socket_set_peer_groups(self._sock, value) 226 227 def set_bufsize(self, rx, tx): 228 capi.nl_socket_set_buffer_size(self._sock, rx, tx) 229 230 def connect(self, proto): 231 capi.nl_connect(self._sock, proto) 232 return self 233 234 def disconnect(self): 235 capi.nl_close(self._sock) 236 237 def sendto(self, buf): 238 ret = capi.nl_sendto(self._sock, buf, len(buf)) 239 if ret < 0: 240 raise Exception('Failed to send') 241 else: 242 return ret 243 244 def send_auto_complete(self, msg): 245 if not isinstance(msg, Message): 246 raise Exception('must provide Message instance') 247 ret = capi.nl_send_auto_complete(self._sock, msg._msg) 248 if ret < 0: 249 raise Exception('send_auto_complete failed: ret=%d' % ret) 250 return ret 251 252 def recvmsgs(self, recv_cb): 253 if not isinstance(recv_cb, Callback): 254 raise Exception('must provide Callback instance') 255 ret = capi.nl_recvmsgs(self._sock, recv_cb._cb) 256 if ret < 0: 257 raise Exception('recvmsg failed: ret=%d' % ret) 258 259_sockets = {} 260 261def lookup_socket(protocol): 262 try: 263 sock = _sockets[protocol] 264 except KeyError: 265 sock = Socket() 266 sock.connect(protocol) 267 _sockets[protocol] = sock 268 269 return sock 270 271class DumpParams(object): 272 """Dumping parameters""" 273 274 def __init__(self, type_=NL_DUMP_LINE): 275 self._dp = capi.alloc_dump_params() 276 if not self._dp: 277 raise Exception('Unable to allocate struct nl_dump_params') 278 279 self._dp.dp_type = type_ 280 281 def __del__(self): 282 capi.free_dump_params(self._dp) 283 284 @property 285 def type(self): 286 return self._dp.dp_type 287 288 @type.setter 289 def type(self, value): 290 self._dp.dp_type = value 291 292 @property 293 def prefix(self): 294 return self._dp.dp_prefix 295 296 @prefix.setter 297 def prefix(self, value): 298 self._dp.dp_prefix = value 299 300# underscore this to make sure it is deleted first upon module deletion 301_defaultDumpParams = DumpParams(NL_DUMP_LINE) 302 303class Object(object): 304 """Cacheable object (base class)""" 305 306 def __init__(self, obj_name, name, obj=None): 307 self._obj_name = obj_name 308 self._name = name 309 self._modules = [] 310 311 if not obj: 312 obj = capi.object_alloc_name(self._obj_name) 313 314 self._nl_object = obj 315 316 # Create a clone which stores the original state to notice 317 # modifications 318 clone_obj = capi.nl_object_clone(self._nl_object) 319 self._orig = self._obj2type(clone_obj) 320 321 def __del__(self): 322 if not self._nl_object: 323 raise ValueError() 324 325 capi.nl_object_put(self._nl_object) 326 327 def __str__(self): 328 if hasattr(self, 'format'): 329 return self.format() 330 else: 331 return capi.nl_object_dump_buf(self._nl_object, 4096).rstrip() 332 333 def _new_instance(self): 334 raise NotImplementedError() 335 336 def clone(self): 337 """Clone object""" 338 return self._new_instance(capi.nl_object_clone(self._nl_object)) 339 340 def _module_lookup(self, path, constructor=None): 341 """Lookup object specific module and load it 342 343 Object implementations consisting of multiple types may 344 offload some type specific code to separate modules which 345 are loadable on demand, e.g. a VLAN link or a specific 346 queueing discipline implementation. 347 348 Loads the module `path` and calls the constructor if 349 supplied or `module`.init() 350 351 The constructor/init function typically assigns a new 352 object covering the type specific implementation aspects 353 to the new object, e.g. link.vlan = VLANLink() 354 """ 355 try: 356 __import__(path) 357 except ImportError: 358 return 359 360 module = sys.modules[path] 361 362 if constructor: 363 ret = getattr(module, constructor)(self) 364 else: 365 ret = module.init(self) 366 367 if ret: 368 self._modules.append(ret) 369 370 def _module_brief(self): 371 ret = '' 372 373 for module in self._modules: 374 if hasattr(module, 'brief'): 375 ret += module.brief() 376 377 return ret 378 379 def dump(self, params=None): 380 """Dump object as human readable text""" 381 if params is None: 382 params = _defaultDumpParams 383 384 capi.nl_object_dump(self._nl_object, params._dp) 385 386 387 @property 388 def mark(self): 389 return bool(capi.nl_object_is_marked(self._nl_object)) 390 391 @mark.setter 392 def mark(self, value): 393 if value: 394 capi.nl_object_mark(self._nl_object) 395 else: 396 capi.nl_object_unmark(self._nl_object) 397 398 @property 399 def shared(self): 400 return capi.nl_object_shared(self._nl_object) != 0 401 402 @property 403 def attrs(self): 404 attr_list = capi.nl_object_attr_list(self._nl_object, 1024) 405 return attr_list[0].split() 406 407 @property 408 def refcnt(self): 409 return capi.nl_object_get_refcnt(self._nl_object) 410 411 # this method resolves multiple levels of sub types to allow 412 # accessing properties of subclass/subtypes (e.g. link.vlan.id) 413 def _resolve(self, attr): 414 obj = self 415 l = attr.split('.') 416 while len(l) > 1: 417 obj = getattr(obj, l.pop(0)) 418 return (obj, l.pop(0)) 419 420 def _setattr(self, attr, val): 421 obj, attr = self._resolve(attr) 422 return setattr(obj, attr, val) 423 424 def _hasattr(self, attr): 425 obj, attr = self._resolve(attr) 426 return hasattr(obj, attr) 427 428class ObjIterator(object): 429 def __init__(self, cache, obj): 430 self._cache = cache 431 self._nl_object = None 432 433 if not obj: 434 self._end = 1 435 else: 436 capi.nl_object_get(obj) 437 self._nl_object = obj 438 self._first = 1 439 self._end = 0 440 441 def __del__(self): 442 if self._nl_object: 443 capi.nl_object_put(self._nl_object) 444 445 def __iter__(self): 446 return self 447 448 def get_next(self): 449 return capi.nl_cache_get_next(self._nl_object) 450 451 def next(self): 452 return self.__next__() 453 454 def __next__(self): 455 if self._end: 456 raise StopIteration() 457 458 if self._first: 459 ret = self._nl_object 460 self._first = 0 461 else: 462 ret = self.get_next() 463 if not ret: 464 self._end = 1 465 raise StopIteration() 466 467 # return ref of previous element and acquire ref of current 468 # element to have object stay around until we fetched the 469 # next ptr 470 capi.nl_object_put(self._nl_object) 471 capi.nl_object_get(ret) 472 self._nl_object = ret 473 474 # reference used inside object 475 capi.nl_object_get(ret) 476 return self._cache._new_object(ret) 477 478 479class ReverseObjIterator(ObjIterator): 480 def get_next(self): 481 return capi.nl_cache_get_prev(self._nl_object) 482 483class Cache(object): 484 """Collection of netlink objects""" 485 def __init__(self): 486 if self.__class__ is Cache: 487 raise NotImplementedError() 488 self.arg1 = None 489 self.arg2 = None 490 491 def __del__(self): 492 capi.nl_cache_free(self._nl_cache) 493 494 def __len__(self): 495 return capi.nl_cache_nitems(self._nl_cache) 496 497 def __iter__(self): 498 obj = capi.nl_cache_get_first(self._nl_cache) 499 return ObjIterator(self, obj) 500 501 def __reversed__(self): 502 obj = capi.nl_cache_get_last(self._nl_cache) 503 return ReverseObjIterator(self, obj) 504 505 def __contains__(self, item): 506 obj = capi.nl_cache_search(self._nl_cache, item._nl_object) 507 if obj is None: 508 return False 509 else: 510 capi.nl_object_put(obj) 511 return True 512 513 # called by sub classes to allocate type specific caches by name 514 @staticmethod 515 def _alloc_cache_name(name): 516 return capi.alloc_cache_name(name) 517 518 # implemented by sub classes, must return new instasnce of cacheable 519 # object 520 @staticmethod 521 def _new_object(obj): 522 raise NotImplementedError() 523 524 # implemented by sub classes, must return instance of sub class 525 def _new_cache(self, cache): 526 raise NotImplementedError() 527 528 def subset(self, filter_): 529 """Return new cache containing subset of cache 530 531 Cretes a new cache containing all objects which match the 532 specified filter. 533 """ 534 if not filter_: 535 raise ValueError() 536 537 c = capi.nl_cache_subset(self._nl_cache, filter_._nl_object) 538 return self._new_cache(cache=c) 539 540 def dump(self, params=None, filter_=None): 541 """Dump (print) cache as human readable text""" 542 if not params: 543 params = _defaultDumpParams 544 545 if filter_: 546 filter_ = filter_._nl_object 547 548 capi.nl_cache_dump_filter(self._nl_cache, params._dp, filter_) 549 550 def clear(self): 551 """Remove all cache entries""" 552 capi.nl_cache_clear(self._nl_cache) 553 554 # Called by sub classes to set first cache argument 555 def _set_arg1(self, arg): 556 self.arg1 = arg 557 capi.nl_cache_set_arg1(self._nl_cache, arg) 558 559 # Called by sub classes to set second cache argument 560 def _set_arg2(self, arg): 561 self.arg2 = arg 562 capi.nl_cache_set_arg2(self._nl_cache, arg) 563 564 def refill(self, socket=None): 565 """Clear cache and refill it""" 566 if socket is None: 567 socket = lookup_socket(self._protocol) 568 569 capi.nl_cache_refill(socket._sock, self._nl_cache) 570 return self 571 572 def resync(self, socket=None, cb=None, args=None): 573 """Synchronize cache with content in kernel""" 574 if socket is None: 575 socket = lookup_socket(self._protocol) 576 577 capi.nl_cache_resync(socket._sock, self._nl_cache, cb, args) 578 579 def provide(self): 580 """Provide this cache to others 581 582 Caches which have been "provided" are made available 583 to other users (of the same application context) which 584 "require" it. F.e. a link cache is generally provided 585 to allow others to translate interface indexes to 586 link names 587 """ 588 589 capi.nl_cache_mngt_provide(self._nl_cache) 590 591 def unprovide(self): 592 """Unprovide this cache 593 594 No longer make the cache available to others. If the cache 595 has been handed out already, that reference will still 596 be valid. 597 """ 598 capi.nl_cache_mngt_unprovide(self._nl_cache) 599 600# Cache Manager (Work in Progress) 601NL_AUTO_PROVIDE = 1 602class CacheManager(object): 603 def __init__(self, protocol, flags=None): 604 605 self._sock = Socket() 606 self._sock.connect(protocol) 607 608 if not flags: 609 flags = NL_AUTO_PROVIDE 610 611 self._mngr = capi.cache_mngr_alloc(self._sock._sock, protocol, flags) 612 613 def __del__(self): 614 if self._sock: 615 self._sock.disconnect() 616 617 if self._mngr: 618 capi.nl_cache_mngr_free(self._mngr) 619 620 def add(self, name): 621 capi.cache_mngr_add(self._mngr, name, None, None) 622 623class AddressFamily(object): 624 """Address family representation 625 626 af = AddressFamily('inet6') 627 # raises: 628 # - ValueError if family name is not known 629 # - TypeError if invalid type is specified for family 630 631 print af # => 'inet6' (string representation) 632 print int(af) # => 10 (numeric representation) 633 print repr(af) # => AddressFamily('inet6') 634 """ 635 def __init__(self, family=socket.AF_UNSPEC): 636 if isinstance(family, str): 637 family = capi.nl_str2af(family) 638 if family < 0: 639 raise ValueError('Unknown family name') 640 elif not isinstance(family, int): 641 raise TypeError() 642 643 self._family = family 644 645 def __str__(self): 646 return capi.nl_af2str(self._family, 32)[0] 647 648 def __int__(self): 649 return self._family 650 651 def __repr__(self): 652 return 'AddressFamily({0!r})'.format(str(self)) 653 654 655class AbstractAddress(object): 656 """Abstract address object 657 658 addr = AbstractAddress('127.0.0.1/8') 659 print addr # => '127.0.0.1/8' 660 print addr.prefixlen # => '8' 661 print addr.family # => 'inet' 662 print len(addr) # => '4' (32bit ipv4 address) 663 664 a = AbstractAddress('10.0.0.1/24') 665 b = AbstractAddress('10.0.0.2/24') 666 print a == b # => False 667 668 669 """ 670 def __init__(self, addr): 671 self._nl_addr = None 672 673 if isinstance(addr, str): 674 # returns None on success I guess 675 # TO CORRECT 676 addr = capi.addr_parse(addr, socket.AF_UNSPEC) 677 if addr is None: 678 raise ValueError('Invalid address format') 679 elif addr: 680 capi.nl_addr_get(addr) 681 682 self._nl_addr = addr 683 684 def __del__(self): 685 if self._nl_addr: 686 capi.nl_addr_put(self._nl_addr) 687 688 def __cmp__(self, other): 689 if isinstance(other, str): 690 other = AbstractAddress(other) 691 692 diff = self.prefixlen - other.prefixlen 693 if diff == 0: 694 diff = capi.nl_addr_cmp(self._nl_addr, other._nl_addr) 695 696 return diff 697 698 def contains(self, item): 699 diff = int(self.family) - int(item.family) 700 if diff: 701 return False 702 703 if item.prefixlen < self.prefixlen: 704 return False 705 706 diff = capi.nl_addr_cmp_prefix(self._nl_addr, item._nl_addr) 707 return diff == 0 708 709 def __nonzero__(self): 710 if self._nl_addr: 711 return not capi.nl_addr_iszero(self._nl_addr) 712 else: 713 return False 714 715 def __len__(self): 716 if self._nl_addr: 717 return capi.nl_addr_get_len(self._nl_addr) 718 else: 719 return 0 720 721 def __str__(self): 722 if self._nl_addr: 723 return capi.nl_addr2str(self._nl_addr, 64)[0] 724 else: 725 return 'none' 726 727 @property 728 def shared(self): 729 """True if address is shared (multiple users)""" 730 if self._nl_addr: 731 return capi.nl_addr_shared(self._nl_addr) != 0 732 else: 733 return False 734 735 @property 736 def prefixlen(self): 737 """Length of prefix (number of bits)""" 738 if self._nl_addr: 739 return capi.nl_addr_get_prefixlen(self._nl_addr) 740 else: 741 return 0 742 743 @prefixlen.setter 744 def prefixlen(self, value): 745 if not self._nl_addr: 746 raise TypeError() 747 748 capi.nl_addr_set_prefixlen(self._nl_addr, int(value)) 749 750 @property 751 def family(self): 752 """Address family""" 753 f = 0 754 if self._nl_addr: 755 f = capi.nl_addr_get_family(self._nl_addr) 756 757 return AddressFamily(f) 758 759 @family.setter 760 def family(self, value): 761 if not self._nl_addr: 762 raise TypeError() 763 764 if not isinstance(value, AddressFamily): 765 value = AddressFamily(value) 766 767 capi.nl_addr_set_family(self._nl_addr, int(value)) 768 769 770# keyword: 771# type = { int | str } 772# immutable = { True | False } 773# fmt = func (formatting function) 774# title = string 775 776def nlattr(**kwds): 777 """netlink object attribute decorator 778 779 decorator used to mark mutable and immutable properties 780 of netlink objects. All properties marked as such are 781 regarded to be accessable. 782 783 @property 784 @netlink.nlattr(type=int) 785 def my_attr(self): 786 return self._my_attr 787 788 """ 789 790 def wrap_fn(func): 791 func.formatinfo = kwds 792 return func 793 return wrap_fn 794