You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
794 lines
20 KiB
794 lines
20 KiB
#
|
|
# 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
|