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.

368 lines
11 KiB

#
# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
#
"""Module providing access to network addresses
"""
from __future__ import absolute_import
__version__ = '1.0'
__all__ = [
'AddressCache',
'Address']
import datetime
from .. import core as netlink
from . import capi as capi
from . import link as Link
from .. import util as util
class AddressCache(netlink.Cache):
"""Cache containing network addresses"""
def __init__(self, cache=None):
if not cache:
cache = self._alloc_cache_name('route/addr')
self._protocol = netlink.NETLINK_ROUTE
self._nl_cache = cache
def __getitem__(self, key):
# Using ifindex=0 here implies that the local address itself
# is unique, otherwise the first occurence is returned.
return self.lookup(0, key)
def lookup(self, ifindex, local):
if type(local) is str:
local = netlink.AbstractAddress(local)
addr = capi.rtnl_addr_get(self._nl_cache, ifindex,
local._nl_addr)
if addr is None:
raise KeyError()
return Address._from_capi(addr)
@staticmethod
def _new_object(obj):
return Address(obj)
@staticmethod
def _new_cache(cache):
return AddressCache(cache=cache)
class Address(netlink.Object):
"""Network address"""
def __init__(self, obj=None):
netlink.Object.__init__(self, 'route/addr', 'address', obj)
self._rtnl_addr = self._obj2type(self._nl_object)
@classmethod
def _from_capi(cls, obj):
return cls(capi.addr2obj(obj))
@staticmethod
def _obj2type(obj):
return capi.obj2addr(obj)
def __cmp__(self, other):
# sort by:
# 1. network link
# 2. address family
# 3. local address (including prefixlen)
diff = self.ifindex - other.ifindex
if diff == 0:
diff = self.family - other.family
if diff == 0:
diff = capi.nl_addr_cmp(self.local, other.local)
return diff
@staticmethod
def _new_instance(obj):
return Address(obj)
@property
@netlink.nlattr(type=int, immutable=True, fmt=util.num)
def ifindex(self):
"""interface index"""
return capi.rtnl_addr_get_ifindex(self._rtnl_addr)
@ifindex.setter
def ifindex(self, value):
link = Link.resolve(value)
if not link:
raise ValueError()
self.link = link
@property
@netlink.nlattr(type=str, fmt=util.string)
def link(self):
link = capi.rtnl_addr_get_link(self._rtnl_addr)
if not link:
return None
return Link.Link.from_capi(link)
@link.setter
def link(self, value):
if type(value) is str:
try:
value = Link.resolve(value)
except KeyError:
raise ValueError()
capi.rtnl_addr_set_link(self._rtnl_addr, value._rtnl_link)
# ifindex is immutable but we assume that if _orig does not
# have an ifindex specified, it was meant to be given here
if capi.rtnl_addr_get_ifindex(self._orig) == 0:
capi.rtnl_addr_set_ifindex(self._orig, value.ifindex)
@property
@netlink.nlattr(type=str, fmt=util.string)
def label(self):
"""address label"""
return capi.rtnl_addr_get_label(self._rtnl_addr)
@label.setter
def label(self, value):
capi.rtnl_addr_set_label(self._rtnl_addr, value)
@property
@netlink.nlattr(type=str, fmt=util.string)
def flags(self):
"""Flags
Setting this property will *Not* reset flags to value you supply in
Examples:
addr.flags = '+xxx' # add xxx flag
addr.flags = 'xxx' # exactly the same
addr.flags = '-xxx' # remove xxx flag
addr.flags = [ '+xxx', '-yyy' ] # list operation
"""
flags = capi.rtnl_addr_get_flags(self._rtnl_addr)
return capi.rtnl_addr_flags2str(flags, 256)[0].split(',')
def _set_flag(self, flag):
if flag.startswith('-'):
i = capi.rtnl_addr_str2flags(flag[1:])
capi.rtnl_addr_unset_flags(self._rtnl_addr, i)
elif flag.startswith('+'):
i = capi.rtnl_addr_str2flags(flag[1:])
capi.rtnl_addr_set_flags(self._rtnl_addr, i)
else:
i = capi.rtnl_addr_str2flags(flag)
capi.rtnl_addr_set_flags(self._rtnl_addr, i)
@flags.setter
def flags(self, value):
if type(value) is list:
for flag in value:
self._set_flag(flag)
else:
self._set_flag(value)
@property
@netlink.nlattr(type=int, immutable=True, fmt=util.num)
def family(self):
"""Address family"""
fam = capi.rtnl_addr_get_family(self._rtnl_addr)
return netlink.AddressFamily(fam)
@family.setter
def family(self, value):
if not isinstance(value, netlink.AddressFamily):
value = netlink.AddressFamily(value)
capi.rtnl_addr_set_family(self._rtnl_addr, int(value))
@property
@netlink.nlattr(type=int, fmt=util.num)
def scope(self):
"""Address scope"""
scope = capi.rtnl_addr_get_scope(self._rtnl_addr)
return capi.rtnl_scope2str(scope, 32)[0]
@scope.setter
def scope(self, value):
if type(value) is str:
value = capi.rtnl_str2scope(value)
capi.rtnl_addr_set_scope(self._rtnl_addr, value)
@property
@netlink.nlattr(type=str, immutable=True, fmt=util.addr)
def local(self):
"""Local address"""
a = capi.rtnl_addr_get_local(self._rtnl_addr)
return netlink.AbstractAddress(a)
@local.setter
def local(self, value):
a = netlink.AbstractAddress(value)
capi.rtnl_addr_set_local(self._rtnl_addr, a._nl_addr)
# local is immutable but we assume that if _orig does not
# have a local address specified, it was meant to be given here
if capi.rtnl_addr_get_local(self._orig) is None:
capi.rtnl_addr_set_local(self._orig, a._nl_addr)
@property
@netlink.nlattr(type=str, fmt=util.addr)
def peer(self):
"""Peer address"""
a = capi.rtnl_addr_get_peer(self._rtnl_addr)
return netlink.AbstractAddress(a)
@peer.setter
def peer(self, value):
a = netlink.AbstractAddress(value)
capi.rtnl_addr_set_peer(self._rtnl_addr, a._nl_addr)
@property
@netlink.nlattr(type=str, fmt=util.addr)
def broadcast(self):
"""Broadcast address"""
a = capi.rtnl_addr_get_broadcast(self._rtnl_addr)
return netlink.AbstractAddress(a)
@broadcast.setter
def broadcast(self, value):
a = netlink.AbstractAddress(value)
capi.rtnl_addr_set_broadcast(self._rtnl_addr, a._nl_addr)
@property
@netlink.nlattr(type=str, fmt=util.addr)
def multicast(self):
"""multicast address"""
a = capi.rtnl_addr_get_multicast(self._rtnl_addr)
return netlink.AbstractAddress(a)
@multicast.setter
def multicast(self, value):
try:
a = netlink.AbstractAddress(value)
except ValueError as err:
raise AttributeError('multicast', err)
capi.rtnl_addr_set_multicast(self._rtnl_addr, a._nl_addr)
@property
@netlink.nlattr(type=str, fmt=util.addr)
def anycast(self):
"""anycast address"""
a = capi.rtnl_addr_get_anycast(self._rtnl_addr)
return netlink.AbstractAddress(a)
@anycast.setter
def anycast(self, value):
a = netlink.AbstractAddress(value)
capi.rtnl_addr_set_anycast(self._rtnl_addr, a._nl_addr)
@property
@netlink.nlattr(type=int, immutable=True, fmt=util.num)
def valid_lifetime(self):
"""Valid lifetime"""
msecs = capi.rtnl_addr_get_valid_lifetime(self._rtnl_addr)
if msecs == 0xFFFFFFFF:
return None
else:
return datetime.timedelta(seconds=msecs)
@valid_lifetime.setter
def valid_lifetime(self, value):
capi.rtnl_addr_set_valid_lifetime(self._rtnl_addr, int(value))
@property
@netlink.nlattr(type=int, immutable=True, fmt=util.num)
def preferred_lifetime(self):
"""Preferred lifetime"""
msecs = capi.rtnl_addr_get_preferred_lifetime(self._rtnl_addr)
if msecs == 0xFFFFFFFF:
return None
else:
return datetime.timedelta(seconds=msecs)
@preferred_lifetime.setter
def preferred_lifetime(self, value):
capi.rtnl_addr_set_preferred_lifetime(self._rtnl_addr, int(value))
@property
@netlink.nlattr(type=int, immutable=True, fmt=util.num)
def create_time(self):
"""Creation time"""
hsec = capi.rtnl_addr_get_create_time(self._rtnl_addr)
return datetime.timedelta(milliseconds=10*hsec)
@property
@netlink.nlattr(type=int, immutable=True, fmt=util.num)
def last_update(self):
"""Last update"""
hsec = capi.rtnl_addr_get_last_update_time(self._rtnl_addr)
return datetime.timedelta(milliseconds=10*hsec)
def add(self, socket=None, flags=None):
if not socket:
socket = netlink.lookup_socket(netlink.NETLINK_ROUTE)
if not flags:
flags = netlink.NLM_F_CREATE
ret = capi.rtnl_addr_add(socket._sock, self._rtnl_addr, flags)
if ret < 0:
raise netlink.KernelError(ret)
def delete(self, socket, flags=0):
"""Attempt to delete this address in the kernel"""
ret = capi.rtnl_addr_delete(socket._sock, self._rtnl_addr, flags)
if ret < 0:
raise netlink.KernelError(ret)
###################################################################
# private properties
#
# Used for formatting output. USE AT OWN RISK
@property
def _flags(self):
return ','.join(self.flags)
def format(self, details=False, stats=False, nodev=False, indent=''):
"""Return address as formatted text"""
fmt = util.MyFormatter(self, indent)
buf = fmt.format('{a|local!b}')
if not nodev:
buf += fmt.format(' {a|ifindex}')
buf += fmt.format(' {a|scope}')
if self.label:
buf += fmt.format(' "{a|label}"')
buf += fmt.format(' <{a|_flags}>')
if details:
buf += fmt.nl('\t{t|broadcast} {t|multicast}') \
+ fmt.nl('\t{t|peer} {t|anycast}')
if self.valid_lifetime:
buf += fmt.nl('\t{s|valid-lifetime!k} '\
'{a|valid_lifetime}')
if self.preferred_lifetime:
buf += fmt.nl('\t{s|preferred-lifetime!k} '\
'{a|preferred_lifetime}')
if stats and (self.create_time or self.last_update):
buf += self.nl('\t{s|created!k} {a|create_time}'\
' {s|last-updated!k} {a|last_update}')
return buf