Home | History | Annotate | Download | only in netlink
      1 #
      2 # Netlink interface based on libnl
      3 #
      4 # Copyright (c) 2011 Thomas Graf <tgraf (at] suug.ch>
      5 #
      6 
      7 """netlink library based on libnl
      8 
      9 This module provides an interface to netlink sockets
     10 
     11 The 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 
     22 The 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 
     27 All other classes or functions in this module are considered implementation
     28 details.
     29 """
     30 from __future__ import absolute_import
     31 
     32 
     33 
     34 from . import capi
     35 import sys
     36 import 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
     52 NETLINK_ROUTE = 0
     53 # NETLINK_UNUSED = 1
     54 NETLINK_USERSOCK = 2
     55 NETLINK_FIREWALL = 3
     56 NETLINK_INET_DIAG = 4
     57 NETLINK_NFLOG = 5
     58 NETLINK_XFRM = 6
     59 NETLINK_SELINUX = 7
     60 NETLINK_ISCSI = 8
     61 NETLINK_AUDIT = 9
     62 NETLINK_FIB_LOOKUP = 10
     63 NETLINK_CONNECTOR = 11
     64 NETLINK_NETFILTER = 12
     65 NETLINK_IP6_FW = 13
     66 NETLINK_DNRTMSG = 14
     67 NETLINK_KOBJECT_UEVENT = 15
     68 NETLINK_GENERIC = 16
     69 NETLINK_SCSITRANSPORT = 18
     70 NETLINK_ECRYPTFS = 19
     71 
     72 NL_DONTPAD = 0
     73 NL_AUTO_PORT = 0
     74 NL_AUTO_SEQ = 0
     75 
     76 NL_DUMP_LINE = 0
     77 NL_DUMP_DETAILS = 1
     78 NL_DUMP_STATS = 2
     79 
     80 NLM_F_REQUEST = 1
     81 NLM_F_MULTI = 2
     82 NLM_F_ACK = 4
     83 NLM_F_ECHO = 8
     84 
     85 NLM_F_ROOT = 0x100
     86 NLM_F_MATCH = 0x200
     87 NLM_F_ATOMIC = 0x400
     88 NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
     89 
     90 NLM_F_REPLACE = 0x100
     91 NLM_F_EXCL = 0x200
     92 NLM_F_CREATE = 0x400
     93 NLM_F_APPEND = 0x800
     94 
     95 class 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 
    103 class KernelError(NetlinkError):
    104     def __str__(self):
    105         return 'Kernel returned: {0}'.format(self._msg)
    106 
    107 class 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 
    114 class 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 
    159 class 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 
    183 class 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 
    261 def 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 
    271 class 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 
    303 class 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 
    428 class 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 
    479 class ReverseObjIterator(ObjIterator):
    480     def get_next(self):
    481         return capi.nl_cache_get_prev(self._nl_object)
    482 
    483 class 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)
    601 NL_AUTO_PROVIDE = 1
    602 class 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 
    623 class 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 
    655 class 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 
    776 def 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