123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793 |
- #
- # 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
- - Message -- The netlink message
- - Callback -- The netlink callback handler
- - 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.
- """
- from __future__ import absolute_import
- from . import capi
- import sys
- import socket
- __all__ = [
- 'Socket',
- 'Message',
- 'Callback',
- '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: {0}'.format(self._msg)
- class ImmutableError(NetlinkError):
- def __init__(self, msg):
- self._msg = msg
- def __str__(self):
- return 'Immutable attribute: {0}'.format(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, sock):
- sock.send(self)
- class Callback(object):
- """Netlink callback"""
- def __init__(self, kind=capi.NL_CB_DEFAULT):
- if isinstance(kind, Callback):
- self._cb = capi.py_nl_cb_clone(kind._cb)
- else:
- self._cb = capi.nl_cb_alloc(kind)
- def __del__(self):
- capi.py_nl_cb_put(self._cb)
- def set_type(self, t, k, handler, obj):
- return capi.py_nl_cb_set(self._cb, t, k, handler, obj)
- def set_all(self, k, handler, obj):
- return capi.py_nl_cb_set_all(self._cb, k, handler, obj)
- def set_err(self, k, handler, obj):
- return capi.py_nl_cb_err(self._cb, k, handler, obj)
- def clone(self):
- return Callback(self)
- class Socket(object):
- """Netlink socket"""
- def __init__(self, cb=None):
- if isinstance(cb, Callback):
- self._sock = capi.nl_socket_alloc_cb(cb._cb)
- elif cb == None:
- self._sock = capi.nl_socket_alloc()
- else:
- raise Exception('\'cb\' parameter has wrong type')
- 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<{0}>'.format(self.local_port)
- @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
- def send_auto_complete(self, msg):
- if not isinstance(msg, Message):
- raise Exception('must provide Message instance')
- ret = capi.nl_send_auto_complete(self._sock, msg._msg)
- if ret < 0:
- raise Exception('send_auto_complete failed: ret=%d' % ret)
- return ret
- def recvmsgs(self, recv_cb):
- if not isinstance(recv_cb, Callback):
- raise Exception('must provide Callback instance')
- ret = capi.nl_recvmsgs(self._sock, recv_cb._cb)
- if ret < 0:
- raise Exception('recvmsg failed: ret=%d' % 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(NL_DUMP_LINE)
- class Object(object):
- """Cacheable object (base class)"""
- def __init__(self, obj_name, name, obj=None):
- self._obj_name = obj_name
- self._name = name
- self._modules = []
- 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 _module_lookup(self, path, constructor=None):
- """Lookup object specific module and load it
- Object implementations consisting of multiple types may
- offload some type specific code to separate modules which
- are loadable on demand, e.g. a VLAN link or a specific
- queueing discipline implementation.
- Loads the module `path` and calls the constructor if
- supplied or `module`.init()
- The constructor/init function typically assigns a new
- object covering the type specific implementation aspects
- to the new object, e.g. link.vlan = VLANLink()
- """
- try:
- __import__(path)
- except ImportError:
- return
- module = sys.modules[path]
- if constructor:
- ret = getattr(module, constructor)(self)
- else:
- ret = module.init(self)
- if ret:
- self._modules.append(ret)
- def _module_brief(self):
- ret = ''
- for module in self._modules:
- if hasattr(module, 'brief'):
- ret += module.brief()
- return ret
- 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)
- @property
- def mark(self):
- return bool(capi.nl_object_is_marked(self._nl_object))
- @mark.setter
- def mark(self, value):
- if value:
- capi.nl_object_mark(self._nl_object)
- else:
- capi.nl_object_unmark(self._nl_object)
- @property
- def shared(self):
- return capi.nl_object_shared(self._nl_object) != 0
- @property
- def attrs(self):
- attr_list = capi.nl_object_attr_list(self._nl_object, 1024)
- return attr_list[0].split()
- @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)
- class ObjIterator(object):
- def __init__(self, cache, obj):
- self._cache = cache
- self._nl_object = None
- if not obj:
- self._end = 1
- else:
- 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):
- return self.__next__()
- 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)
- class Cache(object):
- """Collection of netlink objects"""
- def __init__(self):
- if self.__class__ is Cache:
- raise NotImplementedError()
- self.arg1 = None
- self.arg2 = None
- def __del__(self):
- capi.nl_cache_free(self._nl_cache)
- def __len__(self):
- return capi.nl_cache_nitems(self._nl_cache)
- def __iter__(self):
- obj = capi.nl_cache_get_first(self._nl_cache)
- return ObjIterator(self, obj)
- def __reversed__(self):
- obj = capi.nl_cache_get_last(self._nl_cache)
- return ReverseObjIterator(self, obj)
- def __contains__(self, item):
- obj = capi.nl_cache_search(self._nl_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
- @staticmethod
- def _alloc_cache_name(name):
- return capi.alloc_cache_name(name)
- # implemented by sub classes, must return new instasnce of cacheable
- # object
- @staticmethod
- def _new_object(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._nl_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._nl_cache, params._dp, filter_)
- def clear(self):
- """Remove all cache entries"""
- capi.nl_cache_clear(self._nl_cache)
- # Called by sub classes to set first cache argument
- def _set_arg1(self, arg):
- self.arg1 = arg
- capi.nl_cache_set_arg1(self._nl_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._nl_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._nl_cache)
- return self
- def resync(self, socket=None, cb=None, args=None):
- """Synchronize cache with content in kernel"""
- if socket is None:
- socket = lookup_socket(self._protocol)
- capi.nl_cache_resync(socket._sock, self._nl_cache, cb, args)
- 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._nl_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._nl_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 = capi.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)
- 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 __int__(self):
- return self._family
- def __repr__(self):
- return 'AddressFamily({0!r})'.format(str(self))
- 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):
- # returns None on success I guess
- # TO CORRECT
- 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))
- # keyword:
- # type = { int | str }
- # immutable = { True | False }
- # fmt = func (formatting function)
- # title = string
- def nlattr(**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.
- @property
- @netlink.nlattr(type=int)
- def my_attr(self):
- return self._my_attr
- """
- def wrap_fn(func):
- func.formatinfo = kwds
- return func
- return wrap_fn
|