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