• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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