• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
3#
4
5"""Module providing access to network links
6
7This module provides an interface to view configured network links,
8modify them and to add and delete virtual network links.
9
10The following is a basic example:
11    import netlink.core as netlink
12    import netlink.route.link as link
13
14    sock = netlink.Socket()
15    sock.connect(netlink.NETLINK_ROUTE)
16
17    cache = link.LinkCache()	# create new empty link cache
18    cache.refill(sock)		# fill cache with all configured links
19    eth0 = cache['eth0']		# lookup link "eth0"
20    print eth0			# print basic configuration
21
22The module contains the following public classes:
23
24  - Link -- Represents a network link. Instances can be created directly
25        via the constructor (empty link objects) or via the refill()
26        method of a LinkCache.
27  - LinkCache -- Derived from netlink.Cache, holds any number of
28         network links (Link instances). Main purpose is to keep
29         a local list of all network links configured in the
30         kernel.
31
32The following public functions exist:
33  - get_from_kernel(socket, name)
34
35"""
36
37from __future__ import absolute_import
38
39__version__ = '0.1'
40__all__ = [
41    'LinkCache',
42    'Link',
43]
44
45import socket
46from .. import core as netlink
47from .. import capi as core_capi
48from .  import capi as capi
49from .links  import inet as inet
50from .. import util as util
51
52# Link statistics definitions
53RX_PACKETS = 0
54TX_PACKETS = 1
55RX_BYTES = 2
56TX_BYTES = 3
57RX_ERRORS = 4
58TX_ERRORS = 5
59RX_DROPPED = 6
60TX_DROPPED = 7
61RX_COMPRESSED = 8
62TX_COMPRESSED = 9
63RX_FIFO_ERR = 10
64TX_FIFO_ERR = 11
65RX_LEN_ERR = 12
66RX_OVER_ERR = 13
67RX_CRC_ERR = 14
68RX_FRAME_ERR = 15
69RX_MISSED_ERR = 16
70TX_ABORT_ERR = 17
71TX_CARRIER_ERR = 18
72TX_HBEAT_ERR = 19
73TX_WIN_ERR = 20
74COLLISIONS = 21
75MULTICAST = 22
76IP6_INPKTS = 23
77IP6_INHDRERRORS = 24
78IP6_INTOOBIGERRORS = 25
79IP6_INNOROUTES = 26
80IP6_INADDRERRORS = 27
81IP6_INUNKNOWNPROTOS = 28
82IP6_INTRUNCATEDPKTS = 29
83IP6_INDISCARDS = 30
84IP6_INDELIVERS = 31
85IP6_OUTFORWDATAGRAMS = 32
86IP6_OUTPKTS = 33
87IP6_OUTDISCARDS = 34
88IP6_OUTNOROUTES = 35
89IP6_REASMTIMEOUT = 36
90IP6_REASMREQDS = 37
91IP6_REASMOKS = 38
92IP6_REASMFAILS = 39
93IP6_FRAGOKS = 40
94IP6_FRAGFAILS = 41
95IP6_FRAGCREATES = 42
96IP6_INMCASTPKTS = 43
97IP6_OUTMCASTPKTS = 44
98IP6_INBCASTPKTS = 45
99IP6_OUTBCASTPKTS = 46
100IP6_INOCTETS = 47
101IP6_OUTOCTETS = 48
102IP6_INMCASTOCTETS = 49
103IP6_OUTMCASTOCTETS = 50
104IP6_INBCASTOCTETS = 51
105IP6_OUTBCASTOCTETS = 52
106ICMP6_INMSGS = 53
107ICMP6_INERRORS = 54
108ICMP6_OUTMSGS = 55
109ICMP6_OUTERRORS = 56
110
111class LinkCache(netlink.Cache):
112    """Cache of network links"""
113
114    def __init__(self, family=socket.AF_UNSPEC, cache=None):
115        if not cache:
116            cache = self._alloc_cache_name('route/link')
117
118        self._info_module = None
119        self._protocol = netlink.NETLINK_ROUTE
120        self._nl_cache = cache
121        self._set_arg1(family)
122
123    def __getitem__(self, key):
124        if type(key) is int:
125            link = capi.rtnl_link_get(self._nl_cache, key)
126        else:
127            link = capi.rtnl_link_get_by_name(self._nl_cache, key)
128
129        if link is None:
130            raise KeyError()
131        else:
132            return Link.from_capi(link)
133
134    @staticmethod
135    def _new_object(obj):
136        return Link(obj)
137
138    def _new_cache(self, cache):
139        return LinkCache(family=self.arg1, cache=cache)
140
141class Link(netlink.Object):
142    """Network link"""
143
144    def __init__(self, obj=None):
145        netlink.Object.__init__(self, 'route/link', 'link', obj)
146        self._rtnl_link = self._obj2type(self._nl_object)
147
148        if self.type:
149            self._module_lookup('netlink.route.links.' + self.type)
150
151        self.inet = inet.InetLink(self)
152        self.af = {'inet' : self.inet }
153
154    def __enter__(self):
155        return self
156
157    def __exit__(self, exc_type, exc_value, tb):
158        if exc_type is None:
159            self.change()
160        else:
161            return False
162
163    @classmethod
164    def from_capi(cls, obj):
165        return cls(capi.link2obj(obj))
166
167    @staticmethod
168    def _obj2type(obj):
169        return capi.obj2link(obj)
170
171    def __cmp__(self, other):
172        return self.ifindex - other.ifindex
173
174    @staticmethod
175    def _new_instance(obj):
176        if not obj:
177            raise ValueError()
178
179        return Link(obj)
180
181    @property
182    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
183    def ifindex(self):
184        """interface index"""
185        return capi.rtnl_link_get_ifindex(self._rtnl_link)
186
187    @ifindex.setter
188    def ifindex(self, value):
189        capi.rtnl_link_set_ifindex(self._rtnl_link, int(value))
190
191        # ifindex is immutable but we assume that if _orig does not
192        # have an ifindex specified, it was meant to be given here
193        if capi.rtnl_link_get_ifindex(self._orig) == 0:
194            capi.rtnl_link_set_ifindex(self._orig, int(value))
195
196    @property
197    @netlink.nlattr(type=str, fmt=util.bold)
198    def name(self):
199        """Name of link"""
200        return capi.rtnl_link_get_name(self._rtnl_link)
201
202    @name.setter
203    def name(self, value):
204        capi.rtnl_link_set_name(self._rtnl_link, value)
205
206        # name is the secondary identifier, if _orig does not have
207        # the name specified yet, assume it was meant to be specified
208        # here. ifindex will always take priority, therefore if ifindex
209        # is specified as well, this will be ignored automatically.
210        if capi.rtnl_link_get_name(self._orig) is None:
211            capi.rtnl_link_set_name(self._orig, value)
212
213    @property
214    @netlink.nlattr(type=str, fmt=util.string)
215    def flags(self):
216        """Flags
217        Setting this property will *Not* reset flags to value you supply in
218        Examples:
219        link.flags = '+xxx' # add xxx flag
220        link.flags = 'xxx'  # exactly the same
221        link.flags = '-xxx' # remove xxx flag
222        link.flags = [ '+xxx', '-yyy' ] # list operation
223        """
224        flags = capi.rtnl_link_get_flags(self._rtnl_link)
225        return capi.rtnl_link_flags2str(flags, 256)[0].split(',')
226
227    def _set_flag(self, flag):
228        if flag.startswith('-'):
229            i = capi.rtnl_link_str2flags(flag[1:])
230            capi.rtnl_link_unset_flags(self._rtnl_link, i)
231        elif flag.startswith('+'):
232            i = capi.rtnl_link_str2flags(flag[1:])
233            capi.rtnl_link_set_flags(self._rtnl_link, i)
234        else:
235            i = capi.rtnl_link_str2flags(flag)
236            capi.rtnl_link_set_flags(self._rtnl_link, i)
237
238    @flags.setter
239    def flags(self, value):
240        if not (type(value) is str):
241            for flag in value:
242                self._set_flag(flag)
243        else:
244            self._set_flag(value)
245
246    @property
247    @netlink.nlattr(type=int, fmt=util.num)
248    def mtu(self):
249        """Maximum Transmission Unit"""
250        return capi.rtnl_link_get_mtu(self._rtnl_link)
251
252    @mtu.setter
253    def mtu(self, value):
254        capi.rtnl_link_set_mtu(self._rtnl_link, int(value))
255
256    @property
257    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
258    def family(self):
259        """Address family"""
260        return capi.rtnl_link_get_family(self._rtnl_link)
261
262    @family.setter
263    def family(self, value):
264        capi.rtnl_link_set_family(self._rtnl_link, value)
265
266    @property
267    @netlink.nlattr(type=str, fmt=util.addr)
268    def address(self):
269        """Hardware address (MAC address)"""
270        a = capi.rtnl_link_get_addr(self._rtnl_link)
271        return netlink.AbstractAddress(a)
272
273    @address.setter
274    def address(self, value):
275        capi.rtnl_link_set_addr(self._rtnl_link, value._addr)
276
277    @property
278    @netlink.nlattr(type=str, fmt=util.addr)
279    def broadcast(self):
280        """Hardware broadcast address"""
281        a = capi.rtnl_link_get_broadcast(self._rtnl_link)
282        return netlink.AbstractAddress(a)
283
284    @broadcast.setter
285    def broadcast(self, value):
286        capi.rtnl_link_set_broadcast(self._rtnl_link, value._addr)
287
288    @property
289    @netlink.nlattr(type=str, immutable=True, fmt=util.string)
290    def qdisc(self):
291        """Name of qdisc (cannot be changed)"""
292        return capi.rtnl_link_get_qdisc(self._rtnl_link)
293
294    @qdisc.setter
295    def qdisc(self, value):
296        capi.rtnl_link_set_qdisc(self._rtnl_link, value)
297
298    @property
299    @netlink.nlattr(type=int, fmt=util.num)
300    def txqlen(self):
301        """Length of transmit queue"""
302        return capi.rtnl_link_get_txqlen(self._rtnl_link)
303
304    @txqlen.setter
305    def txqlen(self, value):
306        capi.rtnl_link_set_txqlen(self._rtnl_link, int(value))
307
308    @property
309    @netlink.nlattr(type=str, immutable=True, fmt=util.string)
310    def arptype(self):
311        """Type of link (cannot be changed)"""
312        type_ = capi.rtnl_link_get_arptype(self._rtnl_link)
313        return core_capi.nl_llproto2str(type_, 64)[0]
314
315    @arptype.setter
316    def arptype(self, value):
317        i = core_capi.nl_str2llproto(value)
318        capi.rtnl_link_set_arptype(self._rtnl_link, i)
319
320    @property
321    @netlink.nlattr(type=str, immutable=True, fmt=util.string, title='state')
322    def operstate(self):
323        """Operational status"""
324        operstate = capi.rtnl_link_get_operstate(self._rtnl_link)
325        return capi.rtnl_link_operstate2str(operstate, 32)[0]
326
327    @operstate.setter
328    def operstate(self, value):
329        i = capi.rtnl_link_str2operstate(value)
330        capi.rtnl_link_set_operstate(self._rtnl_link, i)
331
332    @property
333    @netlink.nlattr(type=str, immutable=True, fmt=util.string)
334    def mode(self):
335        """Link mode"""
336        mode = capi.rtnl_link_get_linkmode(self._rtnl_link)
337        return capi.rtnl_link_mode2str(mode, 32)[0]
338
339    @mode.setter
340    def mode(self, value):
341        i = capi.rtnl_link_str2mode(value)
342        capi.rtnl_link_set_linkmode(self._rtnl_link, i)
343
344    @property
345    @netlink.nlattr(type=str, fmt=util.string)
346    def alias(self):
347        """Interface alias (SNMP)"""
348        return capi.rtnl_link_get_ifalias(self._rtnl_link)
349
350    @alias.setter
351    def alias(self, value):
352        capi.rtnl_link_set_ifalias(self._rtnl_link, value)
353
354    @property
355    @netlink.nlattr(type=str, fmt=util.string)
356    def type(self):
357        """Link type"""
358        return capi.rtnl_link_get_type(self._rtnl_link)
359
360    @type.setter
361    def type(self, value):
362        if capi.rtnl_link_set_type(self._rtnl_link, value) < 0:
363            raise NameError('unknown info type')
364
365        self._module_lookup('netlink.route.links.' + value)
366
367    def get_stat(self, stat):
368        """Retrieve statistical information"""
369        if type(stat) is str:
370            stat = capi.rtnl_link_str2stat(stat)
371            if stat < 0:
372                raise NameError('unknown name of statistic')
373
374        return capi.rtnl_link_get_stat(self._rtnl_link, stat)
375
376    def enslave(self, slave, sock=None):
377        if not sock:
378            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
379
380        return capi.rtnl_link_enslave(sock._sock, self._rtnl_link, slave._rtnl_link)
381
382    def release(self, slave, sock=None):
383        if not sock:
384            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
385
386        return capi.rtnl_link_release(sock._sock, self._rtnl_link, slave._rtnl_link)
387
388    def add(self, sock=None, flags=None):
389        if not sock:
390            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
391
392        if not flags:
393            flags = netlink.NLM_F_CREATE
394
395        ret = capi.rtnl_link_add(sock._sock, self._rtnl_link, flags)
396        if ret < 0:
397            raise netlink.KernelError(ret)
398
399    def change(self, sock=None, flags=0):
400        """Commit changes made to the link object"""
401        if sock is None:
402            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
403
404        if not self._orig:
405            raise netlink.NetlinkError('Original link not available')
406        ret = capi.rtnl_link_change(sock._sock, self._orig, self._rtnl_link, flags)
407        if ret < 0:
408            raise netlink.KernelError(ret)
409
410    def delete(self, sock=None):
411        """Attempt to delete this link in the kernel"""
412        if sock is None:
413            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
414
415        ret = capi.rtnl_link_delete(sock._sock, self._rtnl_link)
416        if ret < 0:
417            raise netlink.KernelError(ret)
418
419    ###################################################################
420    # private properties
421    #
422    # Used for formatting output. USE AT OWN RISK
423    @property
424    def _state(self):
425        if 'up' in self.flags:
426            buf = util.good('up')
427            if 'lowerup' not in self.flags:
428                buf += ' ' + util.bad('no-carrier')
429        else:
430            buf = util.bad('down')
431        return buf
432
433    @property
434    def _brief(self):
435        return self._module_brief() + self._foreach_af('brief')
436
437    @property
438    def _flags(self):
439        ignore = [
440            'up',
441            'running',
442            'lowerup',
443        ]
444        return ','.join([flag for flag in self.flags if flag not in ignore])
445
446    def _foreach_af(self, name, args=None):
447        buf = ''
448        for af in self.af:
449            try:
450                func = getattr(self.af[af], name)
451                s = str(func(args))
452                if len(s) > 0:
453                    buf += ' ' + s
454            except AttributeError:
455                pass
456        return buf
457
458    def format(self, details=False, stats=False, indent=''):
459        """Return link as formatted text"""
460        fmt = util.MyFormatter(self, indent)
461
462        buf = fmt.format('{a|ifindex} {a|name} {a|arptype} {a|address} '\
463                 '{a|_state} <{a|_flags}> {a|_brief}')
464
465        if details:
466            buf += fmt.nl('\t{t|mtu} {t|txqlen} {t|weight} '\
467                      '{t|qdisc} {t|operstate}')
468            buf += fmt.nl('\t{t|broadcast} {t|alias}')
469
470            buf += self._foreach_af('details', fmt)
471
472        if stats:
473            l = [['Packets', RX_PACKETS, TX_PACKETS],
474                 ['Bytes', RX_BYTES, TX_BYTES],
475                 ['Errors', RX_ERRORS, TX_ERRORS],
476                 ['Dropped', RX_DROPPED, TX_DROPPED],
477                 ['Compressed', RX_COMPRESSED, TX_COMPRESSED],
478                 ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR],
479                 ['Length Errors', RX_LEN_ERR, None],
480                 ['Over Errors', RX_OVER_ERR, None],
481                 ['CRC Errors', RX_CRC_ERR, None],
482                 ['Frame Errors', RX_FRAME_ERR, None],
483                 ['Missed Errors', RX_MISSED_ERR, None],
484                 ['Abort Errors', None, TX_ABORT_ERR],
485                 ['Carrier Errors', None, TX_CARRIER_ERR],
486                 ['Heartbeat Errors', None, TX_HBEAT_ERR],
487                 ['Window Errors', None, TX_WIN_ERR],
488                 ['Collisions', None, COLLISIONS],
489                 ['Multicast', None, MULTICAST],
490                 ['', None, None],
491                 ['Ipv6:', None, None],
492                 ['Packets', IP6_INPKTS, IP6_OUTPKTS],
493                 ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS],
494                 ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS],
495                 ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS],
496                 ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS],
497                 ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS],
498                 ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS],
499                 ['Delivers', IP6_INDELIVERS, None],
500                 ['Forwarded', None, IP6_OUTFORWDATAGRAMS],
501                 ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES],
502                 ['Header Errors', IP6_INHDRERRORS, None],
503                 ['Too Big Errors', IP6_INTOOBIGERRORS, None],
504                 ['Address Errors', IP6_INADDRERRORS, None],
505                 ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None],
506                 ['Truncated Packets', IP6_INTRUNCATEDPKTS, None],
507                 ['Reasm Timeouts', IP6_REASMTIMEOUT, None],
508                 ['Reasm Requests', IP6_REASMREQDS, None],
509                 ['Reasm Failures', IP6_REASMFAILS, None],
510                 ['Reasm OK', IP6_REASMOKS, None],
511                 ['Frag Created', None, IP6_FRAGCREATES],
512                 ['Frag Failures', None, IP6_FRAGFAILS],
513                 ['Frag OK', None, IP6_FRAGOKS],
514                 ['', None, None],
515                 ['ICMPv6:', None, None],
516                 ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS],
517                 ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]]
518
519            buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'),
520                           15 * ' ', util.title('TX'))
521
522            for row in l:
523                row[0] = util.kw(row[0])
524                row[1] = self.get_stat(row[1]) if row[1] else ''
525                row[2] = self.get_stat(row[2]) if row[2] else ''
526                buf += '\t{0[0]:27} {0[1]:>16} {0[2]:>16}\n'.format(row)
527
528            buf += self._foreach_af('stats')
529
530        return buf
531
532def get(name, sock=None):
533    """Lookup Link object directly from kernel"""
534    if not name:
535        raise ValueError()
536
537    if not sock:
538        sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
539
540    link = capi.get_from_kernel(sock._sock, 0, name)
541    if not link:
542        return None
543
544    return Link.from_capi(link)
545
546_link_cache = LinkCache()
547
548def resolve(name):
549    _link_cache.refill()
550    return _link_cache[name]
551