diff options
Diffstat (limited to 'python/netlink/core.py')
-rw-r--r-- | python/netlink/core.py | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/python/netlink/core.py b/python/netlink/core.py new file mode 100644 index 0000000..f97528d --- /dev/null +++ b/python/netlink/core.py @@ -0,0 +1,737 @@ +# +# Netlink interface based on libnl +# +# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch> +# + +"""netlink library based on libnl + +This module provides an interface to netlink sockets + +The module contains the following public classes: + - Socket -- The netlink socket + - Object -- Abstract object (based on struct nl_obect in libnl) used as + base class for all object types which can be put into a Cache + - Cache -- A collection of objects which are derived from the base + class Object. Used for netlink protocols which maintain a list + or tree of objects. + - DumpParams -- + +The following exceptions are defined: + - NetlinkError -- Base exception for all general purpose exceptions raised. + - KernelError -- Raised when the kernel returns an error as response to a + request. + +All other classes or functions in this module are considered implementation +details. +""" + +import capi +import sys +import socket +import struct + +__all__ = ['Message', 'Socket', 'DumpParams', 'Object', 'Cache', 'KernelError', + 'NetlinkError'] +__version__ = "0.1" + +# netlink protocols +NETLINK_ROUTE = 0 +# NETLINK_UNUSED = 1 +NETLINK_USERSOCK = 2 +NETLINK_FIREWALL = 3 +NETLINK_INET_DIAG = 4 +NETLINK_NFLOG = 5 +NETLINK_XFRM = 6 +NETLINK_SELINUX = 7 +NETLINK_ISCSI = 8 +NETLINK_AUDIT = 9 +NETLINK_FIB_LOOKUP = 10 +NETLINK_CONNECTOR = 11 +NETLINK_NETFILTER = 12 +NETLINK_IP6_FW = 13 +NETLINK_DNRTMSG = 14 +NETLINK_KOBJECT_UEVENT = 15 +NETLINK_GENERIC = 16 +NETLINK_SCSITRANSPORT = 18 +NETLINK_ECRYPTFS = 19 + +NL_DONTPAD = 0 +NL_AUTO_PORT = 0 +NL_AUTO_SEQ = 0 + +NL_DUMP_LINE = 0 +NL_DUMP_DETAILS = 1 +NL_DUMP_STATS = 2 + +NLM_F_REQUEST = 1 +NLM_F_MULTI = 2 +NLM_F_ACK = 4 +NLM_F_ECHO = 8 + +NLM_F_ROOT = 0x100 +NLM_F_MATCH = 0x200 +NLM_F_ATOMIC = 0x400 +NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH + +NLM_F_REPLACE = 0x100 +NLM_F_EXCL = 0x200 +NLM_F_CREATE = 0x400 +NLM_F_APPEND = 0x800 + +class NetlinkError(Exception): + def __init__(self, error): + self._error = error + self._msg = capi.nl_geterror(error) + + def __str__(self): + return self._msg + +class KernelError(NetlinkError): + def __str__(self): + return "Kernel returned: " + self._msg + +class ImmutableError(NetlinkError): + def __init__(self, msg): + self._msg = msg + + def __str__(self): + return "Immutable attribute: " + self._msg + +class Message(object): + """Netlink message""" + + def __init__(self, size=0): + if size == 0: + self._msg = capi.nlmsg_alloc() + else: + self._msg = capi.nlmsg_alloc_size(size) + + if self._msg is None: + raise Exception("Message allocation returned NULL") + + def __del__(self): + capi.nlmsg_free(self._msg) + + def __len__(self): + return capi.nlmsg_len(nlmsg_hdr(self._msg)) + + @property + def protocol(self): + return capi.nlmsg_get_proto(self._msg) + + @protocol.setter + def protocol(self, value): + capi.nlmsg_set_proto(self._msg, value) + + @property + def maxSize(self): + return capi.nlmsg_get_max_size(self._msg) + + @property + def hdr(self): + return capi.nlmsg_hdr(self._msg) + + @property + def data(self): + return capi.nlmsg_data(self._msg) + + @property + def attrs(self): + return capi.nlmsg_attrdata(self._msg) + + def send(self, socket): + socket.send(self) + +class Socket(object): + """Netlink socket""" + + def __init__(self, cb=None): + if cb is None: + self._sock = capi.nl_socket_alloc() + else: + self._sock = capi.nl_socket_alloc_cb(cb) + + if self._sock is None: + raise Exception("NULL pointer returned while allocating socket") + + def __del__(self): + capi.nl_socket_free(self._sock) + + def __str__(self): + return "nlsock<" + str(self.localPort) + ">" + + @property + def local_port(self): + return capi.nl_socket_get_local_port(self._sock) + + @local_port.setter + def local_port(self, value): + capi.nl_socket_set_local_port(self._sock, int(value)) + + @property + def peer_port(self): + return capi.nl_socket_get_peer_port(self._sock) + + @peer_port.setter + def peer_port(self, value): + capi.nl_socket_set_peer_port(self._sock, int(value)) + + @property + def peer_groups(self): + return capi.nl_socket_get_peer_groups(self._sock) + + @peer_groups.setter + def peer_groups(self, value): + capi.nl_socket_set_peer_groups(self._sock, value) + + def set_bufsize(self, rx, tx): + capi.nl_socket_set_buffer_size(self._sock, rx, tx) + + def connect(self, proto): + capi.nl_connect(self._sock, proto) + return self + + def disconnect(self): + capi.nl_close(self._sock) + + def sendto(self, buf): + ret = capi.nl_sendto(self._sock, buf, len(buf)) + if ret < 0: + raise Exception("Failed to send") + else: + return ret + +_sockets = {} + +def lookup_socket(protocol): + try: + sock = _sockets[protocol] + except KeyError: + sock = Socket() + sock.connect(protocol) + _sockets[protocol] = sock + + return sock + +class DumpParams(object): + """Dumping parameters""" + + def __init__(self, type=NL_DUMP_LINE): + self._dp = capi.alloc_dump_params() + if not self._dp: + raise Exception("Unable to allocate struct nl_dump_params") + + self._dp.dp_type = type + + def __del__(self): + capi.free_dump_params(self._dp) + + @property + def type(self): + return self._dp.dp_type + + @type.setter + def type(self, value): + self._dp.dp_type = value + + @property + def prefix(self): + return self._dp.dp_prefix + + @prefix.setter + def prefix(self, value): + self._dp.dp_prefix = value + +# underscore this to make sure it is deleted first upon module deletion +_defaultDumpParams = DumpParams(type=NL_DUMP_LINE) + +########################################################################### +# Cacheable Object (Base Class) +class Object(object): + """Cacheable object (base class)""" + + def __init__(self, obj_name, name, obj=None): + self._obj_name = obj_name + self._name = name + + if not obj: + obj = capi.object_alloc_name(self._obj_name) + + self._nl_object = obj + + # Create a clone which stores the original state to notice + # modifications + clone_obj = capi.nl_object_clone(self._nl_object) + self._orig = self._obj2type(clone_obj) + + def __del__(self): + if not self._nl_object: + raise ValueError() + + capi.nl_object_put(self._nl_object) + + def __str__(self): + if hasattr(self, 'format'): + return self.format() + else: + return capi.nl_object_dump_buf(self._nl_object, 4096).rstrip() + + def _new_instance(self): + raise NotImplementedError() + + def clone(self): + """Clone object""" + return self._new_instance(capi.nl_object_clone(self._nl_object)) + + def dump(self, params=None): + """Dump object as human readable text""" + if params is None: + params = _defaultDumpParams + + capi.nl_object_dump(self._nl_object, params._dp) + + ##################################################################### + # mark + @property + def mark(self): + if capi.nl_object_is_marked(self.obj): + return True + else: + return False + + @mark.setter + def mark(self, value): + if value: + capi.nl_object_mark(self._nl_object) + else: + capi.nl_object_unmark(self._nl_object) + + ##################################################################### + # shared + @property + def shared(self): + return capi.nl_object_shared(self._nl_object) != 0 + + ##################################################################### + # attrs + @property + def attrs(self): + attr_list = capi.nl_object_attr_list(self._nl_object, 1024) + return re.split('\W+', attr_list[0]) + + ##################################################################### + # refcnt + @property + def refcnt(self): + return capi.nl_object_get_refcnt(self._nl_object) + + # this method resolves multiple levels of sub types to allow + # accessing properties of subclass/subtypes (e.g. link.vlan.id) + def _resolve(self, attr): + obj = self + l = attr.split('.') + while len(l) > 1: + obj = getattr(obj, l.pop(0)) + return (obj, l.pop(0)) + + def _setattr(self, attr, val): + obj, attr = self._resolve(attr) + return setattr(obj, attr, val) + + def _hasattr(self, attr): + obj, attr = self._resolve(attr) + return hasattr(obj, attr) + + def apply(self, attr, val): + try: + d = attrs[self._name + "." + attr] + except KeyError: + raise KeyError("Unknown " + self._name + + " attribute: " + attr) + + if 'immutable' in d: + raise ImmutableError(attr) + + if not self._hasattr(attr): + raise KeyError("Invalid " + self._name + + " attribute: " + attr) + self._setattr(attr, val) + +class ObjIterator(object): + def __init__(self, cache, obj): + self._cache = cache + + capi.nl_object_get(obj) + self._nl_object = obj + + self._first = 1 + self._end = 0 + + def __del__(self): + if self._nl_object: + capi.nl_object_put(self._nl_object) + + def __iter__(self): + return self + + def get_next(self): + return capi.nl_cache_get_next(self._nl_object) + + def next(self): + if self._end: + raise StopIteration() + + if self._first: + ret = self._nl_object + self._first = 0 + else: + ret = self.get_next() + if not ret: + self._end = 1 + raise StopIteration() + + # return ref of previous element and acquire ref of current + # element to have object stay around until we fetched the + # next ptr + capi.nl_object_put(self._nl_object) + capi.nl_object_get(ret) + self._nl_object = ret + + # reference used inside object + capi.nl_object_get(ret) + return self._cache._new_object(ret) + + +class ReverseObjIterator(ObjIterator): + def get_next(self): + return capi.nl_cache_get_prev(self._nl_object) + +########################################################################### +# Cache +class Cache(object): + """Collection of netlink objects""" + def __init__(self): + raise NotImplementedError() + + def __del(self): + capi.nl_cache_free(self._c_cache) + + def __len__(self): + return capi.nl_cache_nitems(self._c_cache) + + def __iter__(self): + obj = capi.nl_cache_get_first(self._c_cache) + return ObjIterator(self, obj) + + def __reversed__(self): + obj = capi.nl_cache_get_last(self._c_cache) + return ReverseObjIterator(self, obj) + + def __contains__(self, item): + obj = capi.nl_cache_search(self._c_cache, item._nl_object) + if obj is None: + return False + else: + capi.nl_object_put(obj) + return True + + # called by sub classes to allocate type specific caches by name + def _alloc_cache_name(self, name): + return capi.alloc_cache_name(name) + + # implemented by sub classes, must return new instasnce of cacheable + # object + def _new_object(self, obj): + raise NotImplementedError() + + # implemented by sub classes, must return instance of sub class + def _new_cache(self, cache): + raise NotImplementedError() + + def subset(self, filter): + """Return new cache containing subset of cache + + Cretes a new cache containing all objects which match the + specified filter. + """ + if not filter: + raise ValueError() + + c = capi.nl_cache_subset(self._c_cache, filter._nl_object) + return self._new_cache(cache=c) + + def dump(self, params=None, filter=None): + """Dump (print) cache as human readable text""" + if not params: + params = _defaultDumpParams + + if filter: + filter = filter._nl_object + + capi.nl_cache_dump_filter(self._c_cache, params._dp, filter) + + def clear(self): + """Remove all cache entries""" + capi.nl_cache_clear(self._c_cache) + + # Called by sub classes to set first cache argument + def _set_arg1(self, arg): + self.arg1 = arg + capi.nl_cache_set_arg1(self._c_cache, arg) + + # Called by sub classes to set second cache argument + def _set_arg2(self, arg): + self.arg2 = arg + capi.nl_cache_set_arg2(self._c_cache, arg) + + def refill(self, socket=None): + """Clear cache and refill it""" + if socket is None: + socket = lookup_socket(self._protocol) + + capi.nl_cache_refill(socket._sock, self._c_cache) + return self + + def resync(self, socket=None, cb=None): + """Synchronize cache with content in kernel""" + if socket is None: + socket = lookup_socket(self._protocol) + + capi.nl_cache_resync(socket._sock, self._c_cache, cb) + + def provide(self): + """Provide this cache to others + + Caches which have been "provided" are made available + to other users (of the same application context) which + "require" it. F.e. a link cache is generally provided + to allow others to translate interface indexes to + link names + """ + + capi.nl_cache_mngt_provide(self._c_cache) + + def unprovide(self): + """Unprovide this cache + + No longer make the cache available to others. If the cache + has been handed out already, that reference will still + be valid. + """ + capi.nl_cache_mngt_unprovide(self._c_cache) + +########################################################################### +# Cache Manager (Work in Progress) +NL_AUTO_PROVIDE = 1 +class CacheManager(object): + def __init__(self, protocol, flags=None): + + self._sock = Socket() + self._sock.connect(protocol) + + if not flags: + flags = NL_AUTO_PROVIDE + + self._mngr = cache_mngr_alloc(self._sock._sock, protocol, flags) + + def __del__(self): + if self._sock: + self._sock.disconnect() + + if self._mngr: + capi.nl_cache_mngr_free(self._mngr) + + def add(self, name): + capi.cache_mngr_add(self._mngr, name, None, None) + +########################################################################### +# Address Family +class AddressFamily(object): + """Address family representation + + af = AddressFamily('inet6') + # raises: + # - ValueError if family name is not known + # - TypeError if invalid type is specified for family + + print af # => 'inet6' (string representation) + print int(af) # => 10 (numeric representation) + print repr(af) # => AddressFamily('inet6') + """ + def __init__(self, family=socket.AF_UNSPEC): + if isinstance(family, str): + family = capi.nl_str2af(family) + if family < 0: + raise ValueError('Unknown family name') + elif not isinstance(family, int): + raise TypeError() + + self._family = family + + def __str__(self): + return capi.nl_af2str(self._family, 32)[0] + + def __len__(self): + return len(str(self)) + + def __int__(self): + return self._family + + def __repr__(self): + return 'AddressFamily(\'' + str(self) + '\')' + + +########################################################################### +# Abstract Address +class AbstractAddress(object): + """Abstract address object + + addr = AbstractAddress('127.0.0.1/8') + print addr # => '127.0.0.1/8' + print addr.prefixlen # => '8' + print addr.family # => 'inet' + print len(addr) # => '4' (32bit ipv4 address) + + a = AbstractAddress('10.0.0.1/24') + b = AbstractAddress('10.0.0.2/24') + print a == b # => False + + + """ + def __init__(self, addr): + self._nl_addr = None + + if isinstance(addr, str): + addr = capi.addr_parse(addr, socket.AF_UNSPEC) + if addr is None: + raise ValueError('Invalid address format') + elif addr: + capi.nl_addr_get(addr) + + self._nl_addr = addr + + def __del__(self): + if self._nl_addr: + capi.nl_addr_put(self._nl_addr) + + def __cmp__(self, other): + if isinstance(other, str): + other = AbstractAddress(other) + + diff = self.prefixlen - other.prefixlen + if diff == 0: + diff = capi.nl_addr_cmp(self._nl_addr, other._nl_addr) + + return diff + + def contains(self, item): + diff = int(self.family) - int(item.family) + if diff: + return False + + if item.prefixlen < self.prefixlen: + return False + + diff = capi.nl_addr_cmp_prefix(self._nl_addr, item._nl_addr) + return diff == 0 + + def __nonzero__(self): + if self._nl_addr: + return not capi.nl_addr_iszero(self._nl_addr) + else: + return False + + def __len__(self): + if self._nl_addr: + return capi.nl_addr_get_len(self._nl_addr) + else: + return 0 + + def __str__(self): + if self._nl_addr: + return capi.nl_addr2str(self._nl_addr, 64)[0] + else: + return "none" + + @property + def shared(self): + """True if address is shared (multiple users)""" + if self._nl_addr: + return capi.nl_addr_shared(self._nl_addr) != 0 + else: + return False + + @property + def prefixlen(self): + """Length of prefix (number of bits)""" + if self._nl_addr: + return capi.nl_addr_get_prefixlen(self._nl_addr) + else: + return 0 + + @prefixlen.setter + def prefixlen(self, value): + if not self._nl_addr: + raise TypeError() + + capi.nl_addr_set_prefixlen(self._nl_addr, int(value)) + + @property + def family(self): + """Address family""" + f = 0 + if self._nl_addr: + f = capi.nl_addr_get_family(self._nl_addr) + + return AddressFamily(f) + + @family.setter + def family(self, value): + if not self._nl_addr: + raise TypeError() + + if not isinstance(value, AddressFamily): + value = AddressFamily(value) + + capi.nl_addr_set_family(self._nl_addr, int(value)) + + +# global dictionay for all object attributes +# +# attrs[type][keyword] : value +# +# keyword: +# type = { int | str } +# immutable = { True | False } +# fmt = func (formatting function) +# +attrs = {} + +def attr(name, **kwds): + attrs[name] = {} + for k in kwds: + attrs[name][k] = kwds[k] + +def nlattr(name, **kwds): + """netlink object attribute decorator + + decorator used to mark mutable and immutable properties + of netlink objects. All properties marked as such are + regarded to be accessable. + + @netlink.nlattr('my_type.my_attr', type=int) + @property + def my_attr(self): + return self._my_attr + + """ + attrs[name] = {} + for k in kwds: + attrs[name][k] = kwds[k] + + def wrap_fn(func): + return func + + return wrap_fn + |