xref: /aosp_15_r20/external/libnl/python/netlink/core.py (revision 4dc78e53d49367fa8e61b07018507c90983a077d)
1#
2# Netlink interface based on libnl
3#
4# Copyright (c) 2011 Thomas Graf <[email protected]>
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
33from . import capi
34import sys
35import socket
36
37__all__ = [
38    "Socket",
39    "Message",
40    "Callback",
41    "DumpParams",
42    "Object",
43    "Cache",
44    "KernelError",
45    "NetlinkError",
46]
47
48__version__ = "0.1"
49
50# netlink protocols
51NETLINK_ROUTE = 0
52# NETLINK_UNUSED = 1
53NETLINK_USERSOCK = 2
54NETLINK_FIREWALL = 3
55NETLINK_INET_DIAG = 4
56NETLINK_NFLOG = 5
57NETLINK_XFRM = 6
58NETLINK_SELINUX = 7
59NETLINK_ISCSI = 8
60NETLINK_AUDIT = 9
61NETLINK_FIB_LOOKUP = 10
62NETLINK_CONNECTOR = 11
63NETLINK_NETFILTER = 12
64NETLINK_IP6_FW = 13
65NETLINK_DNRTMSG = 14
66NETLINK_KOBJECT_UEVENT = 15
67NETLINK_GENERIC = 16
68NETLINK_SCSITRANSPORT = 18
69NETLINK_ECRYPTFS = 19
70
71NL_DONTPAD = 0
72NL_AUTO_PORT = 0
73NL_AUTO_SEQ = 0
74
75NL_DUMP_LINE = 0
76NL_DUMP_DETAILS = 1
77NL_DUMP_STATS = 2
78
79NLM_F_REQUEST = 1
80NLM_F_MULTI = 2
81NLM_F_ACK = 4
82NLM_F_ECHO = 8
83
84NLM_F_ROOT = 0x100
85NLM_F_MATCH = 0x200
86NLM_F_ATOMIC = 0x400
87NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
88
89NLM_F_REPLACE = 0x100
90NLM_F_EXCL = 0x200
91NLM_F_CREATE = 0x400
92NLM_F_APPEND = 0x800
93
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
103
104class KernelError(NetlinkError):
105    def __str__(self):
106        return "Kernel returned: {0}".format(self._msg)
107
108
109class ImmutableError(NetlinkError):
110    def __init__(self, msg):
111        self._msg = msg
112
113    def __str__(self):
114        return "Immutable attribute: {0}".format(self._msg)
115
116
117class Message(object):
118    """Netlink message"""
119
120    def __init__(self, size=0):
121        if size == 0:
122            self._msg = capi.nlmsg_alloc()
123        else:
124            self._msg = capi.nlmsg_alloc_size(size)
125
126        if self._msg is None:
127            raise Exception("Message allocation returned NULL")
128
129    def __del__(self):
130        capi.nlmsg_free(self._msg)
131
132    def __len__(self):
133        return capi.nlmsg_len(capi.nlmsg_hdr(self._msg))
134
135    @property
136    def protocol(self):
137        return capi.nlmsg_get_proto(self._msg)
138
139    @protocol.setter
140    def protocol(self, value):
141        capi.nlmsg_set_proto(self._msg, value)
142
143    @property
144    def maxSize(self):
145        return capi.nlmsg_get_max_size(self._msg)
146
147    @property
148    def hdr(self):
149        return capi.nlmsg_hdr(self._msg)
150
151    @property
152    def data(self):
153        return capi.nlmsg_data(self._msg)
154
155    @property
156    def attrs(self):
157        return capi.nlmsg_attrdata(self._msg)
158
159    def send(self, sock):
160        sock.send(self)
161
162
163class Callback(object):
164    """Netlink callback"""
165
166    def __init__(self, kind=capi.NL_CB_DEFAULT):
167        if isinstance(kind, Callback):
168            self._cb = capi.py_nl_cb_clone(kind._cb)
169        else:
170            self._cb = capi.nl_cb_alloc(kind)
171
172    def __del__(self):
173        capi.py_nl_cb_put(self._cb)
174
175    def set_type(self, t, k, handler, obj):
176        return capi.py_nl_cb_set(self._cb, t, k, handler, obj)
177
178    def set_all(self, k, handler, obj):
179        return capi.py_nl_cb_set_all(self._cb, k, handler, obj)
180
181    def set_err(self, k, handler, obj):
182        return capi.py_nl_cb_err(self._cb, k, handler, obj)
183
184    def clone(self):
185        return Callback(self)
186
187
188class Socket(object):
189    """Netlink socket"""
190
191    def __init__(self, cb=None):
192        if isinstance(cb, Callback):
193            self._sock = capi.nl_socket_alloc_cb(cb._cb)
194        elif cb is None:
195            self._sock = capi.nl_socket_alloc()
196        else:
197            raise Exception("'cb' parameter has wrong type")
198
199        if self._sock is None:
200            raise Exception("NULL pointer returned while allocating socket")
201
202    def __del__(self):
203        capi.nl_socket_free(self._sock)
204
205    def __str__(self):
206        return "nlsock<{0}>".format(self.local_port)
207
208    @property
209    def local_port(self):
210        return capi.nl_socket_get_local_port(self._sock)
211
212    @local_port.setter
213    def local_port(self, value):
214        capi.nl_socket_set_local_port(self._sock, int(value))
215
216    @property
217    def peer_port(self):
218        return capi.nl_socket_get_peer_port(self._sock)
219
220    @peer_port.setter
221    def peer_port(self, value):
222        capi.nl_socket_set_peer_port(self._sock, int(value))
223
224    @property
225    def peer_groups(self):
226        return capi.nl_socket_get_peer_groups(self._sock)
227
228    @peer_groups.setter
229    def peer_groups(self, value):
230        capi.nl_socket_set_peer_groups(self._sock, value)
231
232    def set_bufsize(self, rx, tx):
233        capi.nl_socket_set_buffer_size(self._sock, rx, tx)
234
235    def connect(self, proto):
236        capi.nl_connect(self._sock, proto)
237        return self
238
239    def disconnect(self):
240        capi.nl_close(self._sock)
241
242    def sendto(self, buf):
243        ret = capi.nl_sendto(self._sock, buf, len(buf))
244        if ret < 0:
245            raise Exception("Failed to send")
246        else:
247            return ret
248
249    def send_auto_complete(self, msg):
250        if not isinstance(msg, Message):
251            raise Exception("must provide Message instance")
252        ret = capi.nl_send_auto_complete(self._sock, msg._msg)
253        if ret < 0:
254            raise Exception("send_auto_complete failed: ret=%d" % ret)
255        return ret
256
257    def recvmsgs(self, recv_cb):
258        if not isinstance(recv_cb, Callback):
259            raise Exception("must provide Callback instance")
260        ret = capi.nl_recvmsgs(self._sock, recv_cb._cb)
261        if ret < 0:
262            raise Exception("recvmsg failed: ret=%d" % ret)
263
264
265_sockets = {}
266
267
268def lookup_socket(protocol):
269    try:
270        sock = _sockets[protocol]
271    except KeyError:
272        sock = Socket()
273        sock.connect(protocol)
274        _sockets[protocol] = sock
275
276    return sock
277
278
279class DumpParams(object):
280    """Dumping parameters"""
281
282    def __init__(self, type_=NL_DUMP_LINE):
283        self._dp = capi.alloc_dump_params()
284        if not self._dp:
285            raise Exception("Unable to allocate struct nl_dump_params")
286
287        self._dp.dp_type = type_
288
289    def __del__(self):
290        capi.free_dump_params(self._dp)
291
292    @property
293    def type(self):
294        return self._dp.dp_type
295
296    @type.setter
297    def type(self, value):
298        self._dp.dp_type = value
299
300    @property
301    def prefix(self):
302        return self._dp.dp_prefix
303
304    @prefix.setter
305    def prefix(self, value):
306        self._dp.dp_prefix = value
307
308
309# underscore this to make sure it is deleted first upon module deletion
310_defaultDumpParams = DumpParams(NL_DUMP_LINE)
311
312
313class Object(object):
314    """Cacheable object (base class)"""
315
316    def __init__(self, obj_name, name, obj=None):
317        self._obj_name = obj_name
318        self._name = name
319        self._modules = []
320
321        if not obj:
322            obj = capi.object_alloc_name(self._obj_name)
323
324        self._nl_object = obj
325
326        # Create a clone which stores the original state to notice
327        # modifications
328        clone_obj = capi.nl_object_clone(self._nl_object)
329        self._orig = self._obj2type(clone_obj)
330
331    def __del__(self):
332        if not self._nl_object:
333            raise ValueError()
334
335        capi.nl_object_put(self._nl_object)
336
337    def __str__(self):
338        if hasattr(self, "format"):
339            return self.format()
340        else:
341            return capi.nl_object_dump_buf(self._nl_object, 4096).rstrip()
342
343    def _new_instance(self):
344        raise NotImplementedError()
345
346    def clone(self):
347        """Clone object"""
348        return self._new_instance(capi.nl_object_clone(self._nl_object))
349
350    def _module_lookup(self, path, constructor=None):
351        """Lookup object specific module and load it
352
353        Object implementations consisting of multiple types may
354        offload some type specific code to separate modules which
355        are loadable on demand, e.g. a VLAN link or a specific
356        queueing discipline implementation.
357
358        Loads the module `path` and calls the constructor if
359        supplied or `module`.init()
360
361        The constructor/init function typically assigns a new
362        object covering the type specific implementation aspects
363        to the new object, e.g. link.vlan = VLANLink()
364        """
365        try:
366            __import__(path)
367        except ImportError:
368            return
369
370        module = sys.modules[path]
371
372        if constructor:
373            ret = getattr(module, constructor)(self)
374        else:
375            ret = module.init(self)
376
377        if ret:
378            self._modules.append(ret)
379
380    def _module_brief(self):
381        ret = ""
382
383        for module in self._modules:
384            if hasattr(module, "brief"):
385                ret += module.brief()
386
387        return ret
388
389    def dump(self, params=None):
390        """Dump object as human readable text"""
391        if params is None:
392            params = _defaultDumpParams
393
394        capi.nl_object_dump(self._nl_object, params._dp)
395
396    @property
397    def mark(self):
398        return bool(capi.nl_object_is_marked(self._nl_object))
399
400    @mark.setter
401    def mark(self, value):
402        if value:
403            capi.nl_object_mark(self._nl_object)
404        else:
405            capi.nl_object_unmark(self._nl_object)
406
407    @property
408    def shared(self):
409        return capi.nl_object_shared(self._nl_object) != 0
410
411    @property
412    def attrs(self):
413        attr_list = capi.nl_object_attr_list(self._nl_object, 1024)
414        return attr_list[0].split()
415
416    @property
417    def refcnt(self):
418        return capi.nl_object_get_refcnt(self._nl_object)
419
420    # this method resolves multiple levels of sub types to allow
421    # accessing properties of subclass/subtypes (e.g. link.vlan.id)
422    def _resolve(self, attr):
423        obj = self
424        lst = attr.split(".")
425        while len(lst) > 1:
426            obj = getattr(obj, lst.pop(0))
427        return (obj, lst.pop(0))
428
429    def _setattr(self, attr, val):
430        obj, attr = self._resolve(attr)
431        return setattr(obj, attr, val)
432
433    def _hasattr(self, attr):
434        obj, attr = self._resolve(attr)
435        return hasattr(obj, attr)
436
437
438class ObjIterator(object):
439    def __init__(self, cache, obj):
440        self._cache = cache
441        self._nl_object = None
442
443        if not obj:
444            self._end = 1
445        else:
446            capi.nl_object_get(obj)
447            self._nl_object = obj
448            self._first = 1
449            self._end = 0
450
451    def __del__(self):
452        if self._nl_object:
453            capi.nl_object_put(self._nl_object)
454
455    def __iter__(self):
456        return self
457
458    def get_next(self):
459        return capi.nl_cache_get_next(self._nl_object)
460
461    def next(self):
462        return self.__next__()
463
464    def __next__(self):
465        if self._end:
466            raise StopIteration()
467
468        if self._first:
469            ret = self._nl_object
470            self._first = 0
471        else:
472            ret = self.get_next()
473            if not ret:
474                self._end = 1
475                raise StopIteration()
476
477        # return ref of previous element and acquire ref of current
478        # element to have object stay around until we fetched the
479        # next ptr
480        capi.nl_object_put(self._nl_object)
481        capi.nl_object_get(ret)
482        self._nl_object = ret
483
484        # reference used inside object
485        capi.nl_object_get(ret)
486        return self._cache._new_object(ret)
487
488
489class ReverseObjIterator(ObjIterator):
490    def get_next(self):
491        return capi.nl_cache_get_prev(self._nl_object)
492
493
494class Cache(object):
495    """Collection of netlink objects"""
496
497    def __init__(self):
498        if self.__class__ is Cache:
499            raise NotImplementedError()
500        self.arg1 = None
501        self.arg2 = None
502
503    def __del__(self):
504        capi.nl_cache_free(self._nl_cache)
505
506    def __len__(self):
507        return capi.nl_cache_nitems(self._nl_cache)
508
509    def __iter__(self):
510        obj = capi.nl_cache_get_first(self._nl_cache)
511        return ObjIterator(self, obj)
512
513    def __reversed__(self):
514        obj = capi.nl_cache_get_last(self._nl_cache)
515        return ReverseObjIterator(self, obj)
516
517    def __contains__(self, item):
518        obj = capi.nl_cache_search(self._nl_cache, item._nl_object)
519        if obj is None:
520            return False
521        else:
522            capi.nl_object_put(obj)
523            return True
524
525    # called by sub classes to allocate type specific caches by name
526    @staticmethod
527    def _alloc_cache_name(name):
528        return capi.alloc_cache_name(name)
529
530    # implemented by sub classes, must return new instasnce of cacheable
531    # object
532    @staticmethod
533    def _new_object(obj):
534        raise NotImplementedError()
535
536    # implemented by sub classes, must return instance of sub class
537    def _new_cache(self, cache):
538        raise NotImplementedError()
539
540    def subset(self, filter_):
541        """Return new cache containing subset of cache
542
543        Cretes a new cache containing all objects which match the
544        specified filter.
545        """
546        if not filter_:
547            raise ValueError()
548
549        c = capi.nl_cache_subset(self._nl_cache, filter_._nl_object)
550        return self._new_cache(cache=c)
551
552    def dump(self, params=None, filter_=None):
553        """Dump (print) cache as human readable text"""
554        if not params:
555            params = _defaultDumpParams
556
557        if filter_:
558            filter_ = filter_._nl_object
559
560        capi.nl_cache_dump_filter(self._nl_cache, params._dp, filter_)
561
562    def clear(self):
563        """Remove all cache entries"""
564        capi.nl_cache_clear(self._nl_cache)
565
566    # Called by sub classes to set first cache argument
567    def _set_arg1(self, arg):
568        self.arg1 = arg
569        capi.nl_cache_set_arg1(self._nl_cache, arg)
570
571    # Called by sub classes to set second cache argument
572    def _set_arg2(self, arg):
573        self.arg2 = arg
574        capi.nl_cache_set_arg2(self._nl_cache, arg)
575
576    def refill(self, socket=None):
577        """Clear cache and refill it"""
578        if socket is None:
579            socket = lookup_socket(self._protocol)
580
581        capi.nl_cache_refill(socket._sock, self._nl_cache)
582        return self
583
584    def resync(self, socket=None, cb=None, args=None):
585        """Synchronize cache with content in kernel"""
586        if socket is None:
587            socket = lookup_socket(self._protocol)
588
589        capi.nl_cache_resync(socket._sock, self._nl_cache, cb, args)
590
591    def provide(self):
592        """Provide this cache to others
593
594        Caches which have been "provided" are made available
595        to other users (of the same application context) which
596        "require" it. F.e. a link cache is generally provided
597        to allow others to translate interface indexes to
598        link names
599        """
600
601        capi.nl_cache_mngt_provide(self._nl_cache)
602
603    def unprovide(self):
604        """Unprovide this cache
605
606        No longer make the cache available to others. If the cache
607        has been handed out already, that reference will still
608        be valid.
609        """
610        capi.nl_cache_mngt_unprovide(self._nl_cache)
611
612
613# Cache Manager (Work in Progress)
614NL_AUTO_PROVIDE = 1
615
616
617class CacheManager(object):
618    def __init__(self, protocol, flags=None):
619
620        self._sock = Socket()
621        self._sock.connect(protocol)
622
623        if not flags:
624            flags = NL_AUTO_PROVIDE
625
626        self._mngr = capi.cache_mngr_alloc(self._sock._sock, protocol, flags)
627
628    def __del__(self):
629        if self._sock:
630            self._sock.disconnect()
631
632        if self._mngr:
633            capi.nl_cache_mngr_free(self._mngr)
634
635    def add(self, name):
636        capi.cache_mngr_add(self._mngr, name, None, None)
637
638
639class AddressFamily(object):
640    """Address family representation
641
642    af = AddressFamily('inet6')
643    # raises:
644    #   - ValueError if family name is not known
645    #   - TypeError if invalid type is specified for family
646
647    print af        # => 'inet6' (string representation)
648    print int(af)   # => 10 (numeric representation)
649    print repr(af)  # => AddressFamily('inet6')
650    """
651
652    def __init__(self, family=socket.AF_UNSPEC):
653        if isinstance(family, str):
654            family = capi.nl_str2af(family)
655            if family < 0:
656                raise ValueError("Unknown family name")
657        elif not isinstance(family, int):
658            raise TypeError()
659
660        self._family = family
661
662    def __str__(self):
663        return capi.nl_af2str(self._family, 32)[0]
664
665    def __int__(self):
666        return self._family
667
668    def __repr__(self):
669        return "AddressFamily({0!r})".format(str(self))
670
671
672class AbstractAddress(object):
673    """Abstract address object
674
675    addr = AbstractAddress('127.0.0.1/8')
676    print addr               # => '127.0.0.1/8'
677    print addr.prefixlen     # => '8'
678    print addr.family        # => 'inet'
679    print len(addr)          # => '4' (32bit ipv4 address)
680
681    a = AbstractAddress('10.0.0.1/24')
682    b = AbstractAddress('10.0.0.2/24')
683    print a == b             # => False
684
685
686    """
687
688    def __init__(self, addr):
689        self._nl_addr = None
690
691        if isinstance(addr, str):
692            # returns None on success I guess
693            # TO CORRECT
694            addr = capi.addr_parse(addr, socket.AF_UNSPEC)
695            if addr is None:
696                raise ValueError("Invalid address format")
697        elif addr:
698            capi.nl_addr_get(addr)
699
700        self._nl_addr = addr
701
702    def __del__(self):
703        if self._nl_addr:
704            capi.nl_addr_put(self._nl_addr)
705
706    def __cmp__(self, other):
707        if isinstance(other, str):
708            other = AbstractAddress(other)
709
710        diff = self.prefixlen - other.prefixlen
711        if diff == 0:
712            diff = capi.nl_addr_cmp(self._nl_addr, other._nl_addr)
713
714        return diff
715
716    def contains(self, item):
717        diff = int(self.family) - int(item.family)
718        if diff:
719            return False
720
721        if item.prefixlen < self.prefixlen:
722            return False
723
724        diff = capi.nl_addr_cmp_prefix(self._nl_addr, item._nl_addr)
725        return diff == 0
726
727    def __nonzero__(self):
728        if self._nl_addr:
729            return not capi.nl_addr_iszero(self._nl_addr)
730        else:
731            return False
732
733    def __len__(self):
734        if self._nl_addr:
735            return capi.nl_addr_get_len(self._nl_addr)
736        else:
737            return 0
738
739    def __str__(self):
740        if self._nl_addr:
741            return capi.nl_addr2str(self._nl_addr, 64)[0]
742        else:
743            return "none"
744
745    @property
746    def shared(self):
747        """True if address is shared (multiple users)"""
748        if self._nl_addr:
749            return capi.nl_addr_shared(self._nl_addr) != 0
750        else:
751            return False
752
753    @property
754    def prefixlen(self):
755        """Length of prefix (number of bits)"""
756        if self._nl_addr:
757            return capi.nl_addr_get_prefixlen(self._nl_addr)
758        else:
759            return 0
760
761    @prefixlen.setter
762    def prefixlen(self, value):
763        if not self._nl_addr:
764            raise TypeError()
765
766        capi.nl_addr_set_prefixlen(self._nl_addr, int(value))
767
768    @property
769    def family(self):
770        """Address family"""
771        f = 0
772        if self._nl_addr:
773            f = capi.nl_addr_get_family(self._nl_addr)
774
775        return AddressFamily(f)
776
777    @family.setter
778    def family(self, value):
779        if not self._nl_addr:
780            raise TypeError()
781
782        if not isinstance(value, AddressFamily):
783            value = AddressFamily(value)
784
785        capi.nl_addr_set_family(self._nl_addr, int(value))
786
787
788# keyword:
789#   type = { int | str }
790#   immutable = { True | False }
791#   fmt = func (formatting function)
792#   title = string
793
794
795def nlattr(**kwds):
796    """netlink object attribute decorator
797
798    decorator used to mark mutable and immutable properties
799    of netlink objects. All properties marked as such are
800    regarded to be accessable.
801
802    @property
803    @netlink.nlattr(type=int)
804    def my_attr(self):
805        return self._my_attr
806
807    """
808
809    def wrap_fn(func):
810        func.formatinfo = kwds
811        return func
812
813    return wrap_fn
814