mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
3271 lines
128 KiB
Python
3271 lines
128 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2017, 2018 Cumulus Networks, Inc. all rights reserved
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation; version 2.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
# 02110-1301, USA.
|
|
#
|
|
# https://www.gnu.org/licenses/gpl-2.0-standalone.html
|
|
#
|
|
# Author:
|
|
# Julien Fortin, julien@cumulusnetworks.com
|
|
#
|
|
# Netlink cache --
|
|
#
|
|
|
|
import os
|
|
import socket
|
|
import struct
|
|
import signal
|
|
import inspect
|
|
import logging
|
|
import ipaddress
|
|
import threading
|
|
import traceback
|
|
|
|
from logging import DEBUG, WARNING
|
|
from collections import OrderedDict
|
|
|
|
try:
|
|
from ifupdown2.lib.sysfs import Sysfs
|
|
from ifupdown2.lib.base_objects import BaseObject
|
|
from ifupdown2.lib.exceptions import RetryCMD
|
|
|
|
from ifupdown2.nlmanager.nlpacket import \
|
|
Address, \
|
|
Netconf, \
|
|
Link, \
|
|
Route, \
|
|
AF_MPLS, \
|
|
NetlinkPacket, \
|
|
NLM_F_REQUEST, \
|
|
RTM_NEWLINK, \
|
|
RTM_SETLINK, \
|
|
RTM_DELLINK, \
|
|
RTM_NEWADDR, \
|
|
RTM_DELADDR, \
|
|
RTMGRP_ALL, \
|
|
NLMSG_DONE, \
|
|
NLM_F_REQUEST, \
|
|
NLM_F_CREATE, \
|
|
NLM_F_ACK, \
|
|
RT_SCOPES, \
|
|
INFINITY_LIFE_TIME
|
|
|
|
import ifupdown2.nlmanager.ipnetwork as ipnetwork
|
|
import ifupdown2.nlmanager.nlpacket as nlpacket
|
|
import ifupdown2.nlmanager.nllistener as nllistener
|
|
import ifupdown2.nlmanager.nlmanager as nlmanager
|
|
import ifupdown2.ifupdown.statemanager as statemanager
|
|
except (ImportError, ModuleNotFoundError):
|
|
from lib.sysfs import Sysfs
|
|
from lib.base_objects import BaseObject
|
|
from lib.exceptions import RetryCMD
|
|
|
|
from nlmanager.nlpacket import \
|
|
Address, \
|
|
Netconf, \
|
|
Link, \
|
|
Route, \
|
|
AF_MPLS, \
|
|
NetlinkPacket, \
|
|
NLM_F_REQUEST, \
|
|
RTM_NEWLINK, \
|
|
RTM_SETLINK, \
|
|
RTM_DELLINK, \
|
|
RTM_NEWADDR, \
|
|
RTM_DELADDR, \
|
|
RTMGRP_ALL, \
|
|
NLMSG_DONE, \
|
|
NLM_F_REQUEST, \
|
|
NLM_F_CREATE, \
|
|
NLM_F_ACK, \
|
|
RT_SCOPES, \
|
|
INFINITY_LIFE_TIME
|
|
|
|
import nlmanager.ipnetwork as ipnetwork
|
|
import nlmanager.nlpacket as nlpacket
|
|
import nlmanager.nllistener as nllistener
|
|
import nlmanager.nlmanager as nlmanager
|
|
import ifupdown.statemanager as statemanager
|
|
|
|
|
|
log = logging.getLogger()
|
|
|
|
|
|
class NetlinkListenerWithCacheErrorNotInitialized(Exception):
|
|
"""
|
|
If NetlinkListenerWithCache fails on __init__() or / start()
|
|
we need to raise this custom exception.
|
|
"""
|
|
pass
|
|
|
|
|
|
class NetlinkError(Exception):
|
|
def __init__(self, exception, prefix=None, ifname=None):
|
|
netlink_exception_message = ['netlink']
|
|
|
|
if ifname:
|
|
netlink_exception_message.append(ifname)
|
|
|
|
if prefix:
|
|
netlink_exception_message.append(prefix)
|
|
|
|
netlink_exception_message.append(str(exception))
|
|
super(NetlinkError, self).__init__(": ".join(netlink_exception_message))
|
|
|
|
|
|
class NetlinkCacheError(Exception):
|
|
pass
|
|
|
|
|
|
class NetlinkCacheIfnameNotFoundError(NetlinkCacheError):
|
|
pass
|
|
|
|
|
|
class NetlinkCacheIfindexNotFoundError(NetlinkCacheError):
|
|
pass
|
|
|
|
|
|
class _NetlinkCache:
|
|
""" Netlink Cache Class """
|
|
|
|
# we need to store these attributes in a static list to be able to iterate
|
|
# through it when comparing Address objects in add_address()
|
|
# we ignore IFA_CACHEINFO and IFA_FLAGS
|
|
_ifa_attributes = (
|
|
Address.IFA_ADDRESS,
|
|
Address.IFA_LOCAL,
|
|
Address.IFA_LABEL,
|
|
Address.IFA_BROADCAST,
|
|
Address.IFA_ANYCAST,
|
|
# Address.IFA_CACHEINFO,
|
|
Address.IFA_MULTICAST,
|
|
# Address.IFA_FLAGS
|
|
)
|
|
|
|
def __init__(self):
|
|
# sysfs API
|
|
self.__sysfs = Sysfs
|
|
self.__sysfs.cache = self
|
|
|
|
self._link_cache = {}
|
|
self._addr_cache = {}
|
|
self._bridge_vlan_cache = {}
|
|
self._bridge_vlan_vni_cache = {}
|
|
|
|
# helper dictionaries
|
|
# ifindex: ifname
|
|
# ifname: ifindex
|
|
self._ifname_by_ifindex = {}
|
|
self._ifindex_by_ifname = {}
|
|
|
|
self._ifname_by_ifindex_sysfs = {}
|
|
self._ifindex_by_ifname_sysfs = {}
|
|
|
|
# master/slave(s) dictionary
|
|
# master_ifname: [slave_ifname, slave_ifname]
|
|
self._masters_and_slaves = {}
|
|
|
|
# slave/master dictionary
|
|
# slave_ifname: master_ifname
|
|
self._slaves_master = {}
|
|
|
|
# netconf cache data-structure schema:
|
|
# {
|
|
# family: {
|
|
# ifindex: obj
|
|
# }
|
|
# }
|
|
self._netconf_cache = {
|
|
socket.AF_INET: {},
|
|
socket.AF_INET6: {},
|
|
AF_MPLS: {}
|
|
}
|
|
# custom lock mechanism for netconf cache
|
|
self._netconf_cache_lock = threading.Lock()
|
|
|
|
# RLock is needed because we don't want to have separate handling in
|
|
# get_ifname, get_ifindex and all the API function
|
|
self._cache_lock = threading.RLock()
|
|
|
|
# After sending a RTM_DELLINK request (ip link del DEV) we don't
|
|
# automatically receive an RTM_DELLINK notification but instead we
|
|
# have 3 to 5 RTM_NEWLINK notifications (first the device goes
|
|
# admin-down then, goes through other steps that send notifications...
|
|
# Because of this behavior the cache is out of sync and may cause
|
|
# issues. To work-around this behavior we can ignore RTM_NEWLINK for a
|
|
# given ifname until we receive the RTM_DELLINK. That way our cache is
|
|
# not stale. When deleting a link, ifupdown2 uses:
|
|
# - NetlinkListenerWithCache:link_del(ifname)
|
|
# Before sending the RTM_DELLINK netlink packet we:
|
|
# - register the ifname in the _ignore_rtm_newlinkq
|
|
# - force purge the cache because we are not notified right away
|
|
# - for every RTM_NEWLINK notification we check _ignore_rtm_newlinkq
|
|
# to see if we need to ignore that packet
|
|
# - for every RTM_DELLINK notification we check if we have a
|
|
# corresponding entry in _ignore_rtm_newlinkq and remove it
|
|
self._ignore_rtm_newlinkq = list()
|
|
self._ignore_rtm_newlinkq_lock = threading.Lock()
|
|
|
|
# After sending a no master request (IFLA_MASTER=0) the kernels send
|
|
# 2 or 3 notifications (with IFLA_MASTER) before sending the final
|
|
# notification where IFLA_MASTER is removed. For performance purposes
|
|
# we don't wait for those notifications, we simply update the cache
|
|
# to reflect the change (if we got an ACK on the nomaster request).
|
|
# Those extra notification re-add the former slave to it's master
|
|
# (in our internal data-structures at least). ifupdown2 relies on
|
|
# the cache to get accurate information, this puts the cache in an
|
|
# unreliable state. We can detected this bad state and avoid it. Afer
|
|
# a nomaster request we "register" the device as "nomaster", meaning
|
|
# that we will manually remove the IFLA_MASTER attribute from any
|
|
# subsequent packet, until the final packet arrives - then unregister
|
|
# the device from the nomasterq.
|
|
# We need an extra data-structure and lock mechanism for this:
|
|
self._rtm_newlink_nomasterq = list()
|
|
self._rtm_newlink_nomasterq_lock = threading.Lock()
|
|
|
|
# In the scenario of NetlinkListenerWithCache, the listener thread
|
|
# decode netlink packets and perform caching operation based on their
|
|
# respective msgtype add_link for RTM_NEWLINK, remove_link for DELLINK
|
|
# In some cases the main thread is creating a new device with:
|
|
# NetlinkListenerWithCache.link_add()
|
|
# the request is sent and the cache won't have any knowledge of this
|
|
# new link until we receive a NEWLINK notification on the listener
|
|
# socket meanwhile the main thread keeps going. The main thread may
|
|
# query the cache for the newly created device but the cache might not
|
|
# know about it yet thus creating unexpected situation in the main
|
|
# thread operations. We need to provide a mechanism to block the main
|
|
# thread until the desired notification is processed. The main thread
|
|
# can call:
|
|
# register_wait_event(ifname, netlink_msgtype)
|
|
# to register an event for device name 'ifname' and netlink msgtype.
|
|
# The main thread should then call wait_event to sleep until the
|
|
# notification is received the NetlinkListenerWithCache provides the
|
|
# following API:
|
|
# tx_nlpacket_get_response_with_error_and_wait_for_cache(ifname, nl_packet)
|
|
# to handle both packet transmission, error handling and cache event
|
|
self._wait_event = None
|
|
self._wait_event_alarm = threading.Event()
|
|
|
|
def __handle_type_error(self, func_name, data, exception, return_value):
|
|
"""
|
|
TypeError shouldn't happen but if it does, we are prepared to log and recover
|
|
"""
|
|
log.debug('nlcache: %s: %s: TypeError: %s' % (func_name, data, str(exception)))
|
|
return return_value
|
|
|
|
def __unslave_nolock(self, slave, master=None):
|
|
"""
|
|
WARNING: LOCK SHOULD BE ACQUIRED BEFORE CALLING THIS FUNCTION
|
|
|
|
When unslaving a device we need to manually clear and update our internal
|
|
data structures to avoid keeping stale information before receiving a proper
|
|
netlink notification.
|
|
|
|
Dictionaries:
|
|
- master_and_slaves
|
|
- slaves_master
|
|
- bridge_vlan_cache
|
|
|
|
:param master:
|
|
:param slave:
|
|
:return:
|
|
"""
|
|
try:
|
|
del self._link_cache[slave].attributes[Link.IFLA_MASTER]
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
if not master:
|
|
master = self._slaves_master[slave]
|
|
|
|
self._masters_and_slaves[master].remove(slave)
|
|
except (KeyError, ValueError):
|
|
for master, slaves_list in self._masters_and_slaves.items():
|
|
if slave in slaves_list:
|
|
slaves_list.remove(slave)
|
|
break
|
|
|
|
try:
|
|
del self._slaves_master[slave]
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
del self._bridge_vlan_cache[slave]
|
|
except KeyError:
|
|
pass
|
|
|
|
try:
|
|
del self._bridge_vlan_vni_cache[slave]
|
|
except KeyError:
|
|
pass
|
|
|
|
def append_to_ignore_rtm_newlinkq(self, ifname):
|
|
"""
|
|
Register device 'ifname' to the ignore_rtm_newlinkq list pending
|
|
RTM_DELLINK (see comments above _ignore_rtm_newlinkq declaration)
|
|
"""
|
|
with self._ignore_rtm_newlinkq_lock:
|
|
self._ignore_rtm_newlinkq.append(ifname)
|
|
|
|
def remove_from_ignore_rtm_newlinkq(self, ifname):
|
|
""" Unregister ifname from ignore_newlinkq list """
|
|
try:
|
|
with self._ignore_rtm_newlinkq_lock:
|
|
self._ignore_rtm_newlinkq.remove(ifname)
|
|
except ValueError:
|
|
pass
|
|
|
|
def append_to_rtm_newlink_nomasterq(self, ifname):
|
|
""" Register device 'ifname' to the _ignore_rtm_newlink_nomasterq """
|
|
with self._rtm_newlink_nomasterq_lock:
|
|
self._rtm_newlink_nomasterq.append(ifname)
|
|
|
|
def remove_from_rtm_newlink_nomasterq(self, ifname):
|
|
""" Unregister ifname from _ignore_rtm_newlink_nomasterq list """
|
|
try:
|
|
with self._rtm_newlink_nomasterq_lock:
|
|
self._rtm_newlink_nomasterq.remove(ifname)
|
|
except ValueError:
|
|
pass
|
|
|
|
def register_wait_event(self, ifname, msgtype):
|
|
"""
|
|
Register a cache "wait event" for device named 'ifname' and packet
|
|
type msgtype
|
|
|
|
We only one wait_event to be registered. Currently we don't support
|
|
multi-threaded application so we need to had a strict check. In the
|
|
future we could have a wait_event queue for multiple thread could
|
|
register wait event.
|
|
:param ifname: target device
|
|
:param msgtype: netlink message type (RTM_NEWLINK, RTM_DELLINK etc.)
|
|
:return: boolean: did we successfully register a wait_event?
|
|
"""
|
|
with self._cache_lock:
|
|
if self._wait_event:
|
|
return False
|
|
self._wait_event = (ifname, msgtype)
|
|
return True
|
|
|
|
def wait_event(self):
|
|
"""
|
|
Sleep until cache event happened in netlinkq thread or timeout expired
|
|
:return: None
|
|
|
|
We set an arbitrary timeout at 1sec in case the kernel doesn't send
|
|
out a notification for the event we want to wait for.
|
|
"""
|
|
if not self._wait_event_alarm.wait(1):
|
|
log.debug('nlcache: wait event alarm timeout expired for device "%s" and netlink packet type: %s'
|
|
% (self._wait_event[0], NetlinkPacket.type_to_string.get(self._wait_event[1], str(self._wait_event[1]))))
|
|
with self._cache_lock:
|
|
self._wait_event = None
|
|
self._wait_event_alarm.clear()
|
|
|
|
def unregister_wait_event(self):
|
|
"""
|
|
Clear current wait event (cache can only handle one at once)
|
|
:return:
|
|
"""
|
|
with self._cache_lock:
|
|
self._wait_event = None
|
|
self._wait_event_alarm.clear()
|
|
|
|
def override_link_flag(self, ifname, flags):
|
|
# TODO: dont override all the flags just turn on/off IFF_UP
|
|
try:
|
|
with self._cache_lock:
|
|
self._link_cache[ifname].flags = flags
|
|
except Exception:
|
|
pass
|
|
|
|
def override_link_mtu(self, ifname, mtu):
|
|
"""
|
|
Manually override link mtu and ignore any failures
|
|
:param ifname:
|
|
:param mtu:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
self._link_cache[ifname].attributes[Link.IFLA_MTU].value = mtu
|
|
except Exception:
|
|
pass
|
|
|
|
def override_cache_unslave_link(self, slave, master):
|
|
"""
|
|
Manually update the cache unslaving SLAVE from MASTER
|
|
|
|
When calling link_set_nomaster, we don't want to wait for the RTM_GETLINK
|
|
notification - if the operation return with NL_SUCCESS we can manually
|
|
update our cache and move on.
|
|
|
|
:param master:
|
|
:param slave:
|
|
:return:
|
|
"""
|
|
with self._cache_lock:
|
|
self.__unslave_nolock(slave, master)
|
|
|
|
def DEBUG_IFNAME(self, ifname, with_addresses=False):
|
|
"""
|
|
A very useful function to use while debugging, it dumps the netlink
|
|
packet with debug and color output.
|
|
"""
|
|
import logging
|
|
root = logging.getLogger()
|
|
|
|
level = root.level
|
|
|
|
try:
|
|
root.setLevel(DEBUG)
|
|
for handler in root.handlers:
|
|
handler.setLevel(DEBUG)
|
|
|
|
nllistener.log.setLevel(DEBUG)
|
|
nlpacket.log.setLevel(DEBUG)
|
|
nlmanager.log.setLevel(DEBUG)
|
|
with self._cache_lock:
|
|
obj = self._link_cache[ifname]
|
|
save_debug = obj.debug
|
|
obj.debug = True
|
|
obj.dump()
|
|
obj.debug = save_debug
|
|
|
|
#if with_addresses:
|
|
# addrs = self._addr_cache.get(ifname, [])
|
|
# log.error('ADDRESSES=%s' % addrs)
|
|
# for addr in addrs:
|
|
# save_debug = addr.debug
|
|
# addr.debug = True
|
|
# addr.dump()
|
|
# addr.debug = save_debug
|
|
# log.error('-----------')
|
|
# log.error('-----------')
|
|
# log.error('-----------')
|
|
except Exception:
|
|
traceback.print_exc()
|
|
# TODO: save log_level at entry and re-apply it after the dump
|
|
nllistener.log.setLevel(WARNING)
|
|
nlpacket.log.setLevel(WARNING)
|
|
nlmanager.log.setLevel(WARNING)
|
|
|
|
root.setLevel(level)
|
|
for handler in root.handlers:
|
|
handler.setLevel(level)
|
|
|
|
def DEBUG_MSG(self, msg):
|
|
import logging
|
|
root = logging.getLogger()
|
|
level = root.level
|
|
|
|
try:
|
|
root.setLevel(DEBUG)
|
|
for handler in root.handlers:
|
|
handler.setLevel(DEBUG)
|
|
|
|
nllistener.log.setLevel(DEBUG)
|
|
nlpacket.log.setLevel(DEBUG)
|
|
nlmanager.log.setLevel(DEBUG)
|
|
|
|
save_debug = msg.debug
|
|
msg.debug = True
|
|
msg.dump()
|
|
msg.debug = save_debug
|
|
except Exception:
|
|
traceback.print_exc()
|
|
# TODO: save log_level at entry and re-apply it after the dump
|
|
nllistener.log.setLevel(WARNING)
|
|
nlpacket.log.setLevel(WARNING)
|
|
nlmanager.log.setLevel(WARNING)
|
|
|
|
root.setLevel(level)
|
|
for handler in root.handlers:
|
|
handler.setLevel(level)
|
|
|
|
def _populate_sysfs_ifname_ifindex_dicts(self):
|
|
ifname_by_ifindex_dict = {}
|
|
ifindex_by_ifname_dict = {}
|
|
try:
|
|
for dir_name in os.listdir('/sys/class/net/'):
|
|
try:
|
|
with open('/sys/class/net/%s/ifindex' % dir_name) as f:
|
|
ifindex = int(f.readline())
|
|
ifname_by_ifindex_dict[ifindex] = dir_name
|
|
ifindex_by_ifname_dict[dir_name] = ifindex
|
|
except (IOError, ValueError):
|
|
pass
|
|
except OSError:
|
|
pass
|
|
with self._cache_lock:
|
|
self._ifname_by_ifindex_sysfs = ifname_by_ifindex_dict
|
|
self._ifindex_by_ifname_sysfs = ifindex_by_ifname_dict
|
|
|
|
def get_ifindex(self, ifname):
|
|
"""
|
|
Return device index or raise NetlinkCacheIfnameNotFoundError
|
|
:param ifname:
|
|
:return: int
|
|
:raise: NetlinkCacheIfnameNotFoundError(NetlinkCacheError)
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._ifindex_by_ifname[ifname]
|
|
except KeyError:
|
|
# We assume that if the user requested a valid device ifindex but
|
|
# for some reason we don't find any trace of it in our cache, we
|
|
# then use sysfs to make sure that this device exists and fill our
|
|
# internal help dictionaries.
|
|
with self._cache_lock:
|
|
ifindex = self._ifindex_by_ifname_sysfs.get(ifname)
|
|
|
|
if ifindex:
|
|
return ifindex
|
|
self._populate_sysfs_ifname_ifindex_dicts()
|
|
try:
|
|
return self._ifindex_by_ifname_sysfs[ifname]
|
|
except KeyError:
|
|
# if we still haven't found any trace of the requested device
|
|
# we raise a custom exception
|
|
raise NetlinkCacheIfnameNotFoundError('ifname %s not present in cache' % ifname)
|
|
|
|
def get_ifname(self, ifindex):
|
|
"""
|
|
Return device name or raise NetlinkCacheIfindexNotFoundError
|
|
:param ifindex:
|
|
:return: str
|
|
:raise: NetlinkCacheIfindexNotFoundError (NetlinkCacheError)
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._ifname_by_ifindex[ifindex]
|
|
except KeyError:
|
|
# We assume that if the user requested a valid device ifname but
|
|
# for some reason we don't find any trace of it in our cache, we
|
|
# then use sysfs to make sure that this device exists and fill our
|
|
# internal help dictionaries.
|
|
with self._cache_lock:
|
|
ifname = self._ifname_by_ifindex_sysfs.get(ifindex)
|
|
|
|
if ifname:
|
|
return ifname
|
|
self._populate_sysfs_ifname_ifindex_dicts()
|
|
try:
|
|
return self._ifname_by_ifindex_sysfs[ifindex]
|
|
except KeyError:
|
|
# if we still haven't found any trace of the requested device
|
|
# we raise a custom exception
|
|
raise NetlinkCacheIfindexNotFoundError('ifindex %s not present in cache' % ifindex)
|
|
|
|
def link_exists(self, ifname):
|
|
"""
|
|
Check if we have a cache entry for device 'ifname'
|
|
:param ifname: device name
|
|
:return: boolean
|
|
"""
|
|
with self._cache_lock:
|
|
return ifname in self._link_cache
|
|
|
|
def link_is_up(self, ifname):
|
|
"""
|
|
Check if device 'ifname' has IFF_UP flag
|
|
:param ifname:
|
|
:return: boolean
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].flags & Link.IFF_UP
|
|
except (KeyError, TypeError):
|
|
# ifname is not present in the cache
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=False)
|
|
|
|
def link_is_loopback(self, ifname):
|
|
"""
|
|
Check if device has IFF_LOOPBACK flag
|
|
:param ifname:
|
|
:return: boolean
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].flags & Link.IFF_LOOPBACK
|
|
# IFF_LOOPBACK should be enough, otherwise we can also check for
|
|
# link.device_type & Link.ARPHRD_LOOPBACK
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=False)
|
|
|
|
def link_exists_and_up(self, ifname):
|
|
"""
|
|
Check if device exists and has IFF_UP flag set
|
|
:param ifname:
|
|
:return: tuple (boolean, boolean) -> (link_exists, link_is_up)
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return True, self._link_cache[ifname].flags & Link.IFF_UP
|
|
except KeyError:
|
|
# ifname is not present in the cache
|
|
return False, False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=(False, False))
|
|
|
|
def link_is_bridge(self, ifname):
|
|
return self.get_link_kind(ifname) == 'bridge'
|
|
|
|
def get_link_kind(self, ifname):
|
|
"""
|
|
Return link IFLA_INFO_KIND
|
|
:param ifname:
|
|
:return: string
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_KIND]
|
|
except (KeyError, AttributeError):
|
|
return None
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=None)
|
|
|
|
def get_link_mtu(self, ifname):
|
|
"""
|
|
Return link IFLA_MTU
|
|
:param ifname:
|
|
:return: int
|
|
"""
|
|
return self.get_link_attribute(ifname, Link.IFLA_MTU, default=0)
|
|
|
|
def get_link_mtu_str(self, ifname):
|
|
"""
|
|
Return link IFLA_MTU as string
|
|
:param ifname:
|
|
:return: str
|
|
"""
|
|
return str(self.get_link_mtu(ifname))
|
|
|
|
def get_link_address(self, ifname):
|
|
"""
|
|
Return link IFLA_ADDRESS
|
|
:param ifname:
|
|
:return: str
|
|
"""
|
|
packet = None
|
|
default_value = ""
|
|
try:
|
|
with self._cache_lock:
|
|
packet = self._link_cache[ifname]
|
|
return packet.attributes[Link.IFLA_ADDRESS].value.lower()
|
|
except (KeyError, AttributeError):
|
|
# KeyError will be raised if:
|
|
# - ifname is missing from the cache (but link_exists should be called prior this call)
|
|
# - IFLA_ADDRESS is missing
|
|
# AttributeError can also be raised if attributes[IFLA_ADDRESS] returns None
|
|
# If the packet is tagged as a REQUEST packet (priv_flags) we should query sysfs
|
|
# otherwise default_value is returned.
|
|
if packet and packet.priv_flags & NLM_F_REQUEST:
|
|
return self.__sysfs.get_link_address(ifname)
|
|
else:
|
|
return default_value
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), default_value)
|
|
|
|
def get_link_address_raw(self, ifname):
|
|
"""
|
|
Return link IFLA_ADDRESS as integer
|
|
:param ifname:
|
|
:return: int
|
|
"""
|
|
return self.get_link_attribute_raw(ifname, Link.IFLA_ADDRESS, default=0)
|
|
|
|
def get_link_alias(self, ifname):
|
|
"""
|
|
Return link IFLA_IFALIAS
|
|
:param ifname:
|
|
:return: str
|
|
"""
|
|
return self.get_link_attribute(ifname, Link.IFLA_IFALIAS)
|
|
|
|
def get_link_protodown(self, ifname):
|
|
"""
|
|
Return link IFLA_PROTO_DOWN
|
|
:param ifname:
|
|
:return: int
|
|
"""
|
|
return self.get_link_attribute(ifname, Link.IFLA_PROTO_DOWN)
|
|
|
|
def get_link_attribute(self, ifname, attr, default=None):
|
|
"""
|
|
Return link attribute 'attr'.value
|
|
:param ifname:
|
|
:param attr:
|
|
:param default:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[attr].value
|
|
except (KeyError, AttributeError):
|
|
return default
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=default)
|
|
|
|
def get_link_attribute_raw(self, ifname, attr, default=None):
|
|
"""
|
|
Return link attribute 'attr'.raw
|
|
:param ifname:
|
|
:param attr:
|
|
:param default:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[attr].raw
|
|
except (KeyError, AttributeError):
|
|
return default
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=default)
|
|
|
|
def get_link_slave_kind(self, ifname):
|
|
"""
|
|
Return device slave kind
|
|
:param ifname:
|
|
:return: str
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_KIND]
|
|
except (KeyError, AttributeError):
|
|
return None
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=None)
|
|
|
|
def get_link_info_data_attribute(self, ifname, info_data_attribute, default=None):
|
|
"""
|
|
Return device linkinfo:info_data attribute or default value
|
|
:param ifname:
|
|
:param info_data_attribute:
|
|
:param default:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][info_data_attribute]
|
|
except (KeyError, AttributeError):
|
|
return default
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=default)
|
|
|
|
def get_link_info_data(self, ifname):
|
|
"""
|
|
Return device linkinfo:info_data attribute or default value
|
|
:param ifname:
|
|
:param info_data_attribute:
|
|
:param default:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA]
|
|
except (KeyError, AttributeError):
|
|
return {}
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value={})
|
|
|
|
def get_link_info_slave_data_attribute(self, ifname, info_slave_data_attribute, default=None):
|
|
"""
|
|
Return device linkinfo:info_slave_data attribute or default value
|
|
:param ifname:
|
|
:param info_data_attribute:
|
|
:param default:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_DATA][info_slave_data_attribute]
|
|
except (KeyError, AttributeError):
|
|
return default
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=default)
|
|
|
|
################
|
|
# MASTER & SLAVE
|
|
################
|
|
def get_master(self, ifname):
|
|
"""
|
|
Return device master's ifname
|
|
:param ifname:
|
|
:return: str
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._slaves_master[ifname]
|
|
except (KeyError, AttributeError):
|
|
return None
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=None)
|
|
|
|
def get_slaves(self, master):
|
|
"""
|
|
Return all devices ifname enslaved to master device
|
|
:param master:
|
|
:return: list of string
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return list(self._masters_and_slaves[master])
|
|
except KeyError:
|
|
return []
|
|
|
|
def is_link_enslaved_to(self, slave, master):
|
|
"""
|
|
Return bool if SLAVE is enslaved to MASTER
|
|
:param slave:
|
|
:param master:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._slaves_master[slave] == master
|
|
except KeyError:
|
|
return False
|
|
|
|
def get_lower_device_ifname(self, ifname):
|
|
"""
|
|
Return the lower-device (IFLA_LINK) name or raise KeyError
|
|
:param ifname:
|
|
:return: string
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self.get_ifname(self._link_cache[ifname].attributes[Link.IFLA_LINK].value)
|
|
except (NetlinkCacheIfnameNotFoundError, AttributeError, KeyError):
|
|
return None
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=None)
|
|
|
|
##########################################################################
|
|
# VRF ####################################################################
|
|
##########################################################################
|
|
|
|
def get_vrf_table_map(self):
|
|
vrf_table_map = {}
|
|
try:
|
|
with self._cache_lock:
|
|
for ifname, obj in self._link_cache.items():
|
|
linkinfo = obj.attributes.get(Link.IFLA_LINKINFO)
|
|
|
|
if linkinfo and linkinfo.value.get(Link.IFLA_INFO_KIND) == "vrf":
|
|
vrf_table_map[linkinfo.value[Link.IFLA_INFO_DATA][Link.IFLA_VRF_TABLE]] = ifname
|
|
except Exception as e:
|
|
log.debug("get_vrf_table_map: %s" % str(e))
|
|
return vrf_table_map
|
|
|
|
def get_vrf_table(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_VRF_TABLE]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
|
|
##########################################################################
|
|
# BOND ###################################################################
|
|
##########################################################################
|
|
|
|
def bond_exists(self, ifname):
|
|
"""
|
|
Check if bond 'ifname' exists
|
|
:param ifname: bond name
|
|
:return: boolean
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[nlpacket.Link.IFLA_LINKINFO].value[nlpacket.Link.IFLA_INFO_KIND] == 'bond'
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=False)
|
|
|
|
##########################################################################
|
|
# BRIDGE PORT ############################################################
|
|
##########################################################################
|
|
|
|
def get_bridge_port_multicast_router(self, ifname):
|
|
"""
|
|
Get bridge port multicast_router value - defaults to 1
|
|
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
default_value = 1
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_DATA][Link.IFLA_BRPORT_MULTICAST_ROUTER]
|
|
except (KeyError, AttributeError):
|
|
# KeyError will be raised if:
|
|
# - ifname is missing from the cache (but link_exists should be called prior this call)
|
|
# - IFLA_BRPORT_MULTICAST_ROUTER is missing
|
|
# AttributeError can also be raised if IFLA_LINKINFO is missing (None.value)
|
|
# default_value is returned.
|
|
return default_value
|
|
except TypeError as e:
|
|
return self.__handle_type_error(
|
|
inspect.currentframe().f_code.co_name,
|
|
ifname,
|
|
str(e),
|
|
return_value=default_value
|
|
)
|
|
|
|
##########################################################################
|
|
# BRIDGE #################################################################
|
|
##########################################################################
|
|
|
|
def get_bridge_multicast_snooping(self, ifname):
|
|
"""
|
|
Get bridge multicast_snooping value - defaults to 1
|
|
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
default_value = 1
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BR_MCAST_SNOOPING]
|
|
except (KeyError, AttributeError):
|
|
# KeyError will be raised if:
|
|
# - ifname is missing from the cache (but link_exists should be called prior this call)
|
|
# - IFLA_BR_MCAST_SNOOPING is missing
|
|
# AttributeError can also be raised if IFLA_LINKINFO is missing (None.value)
|
|
# default_value is returned.
|
|
return default_value
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=default_value)
|
|
|
|
def get_bridge_stp(self, ifname):
|
|
"""
|
|
WARNING: ifname should be a bridge
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BR_STP_STATE]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=0)
|
|
|
|
def get_brport_cost(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BRPORT_COST]
|
|
except (KeyError, AttributeError):
|
|
return None
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=None)
|
|
|
|
def get_brport_priority(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BRPORT_PRIORITY]
|
|
except (KeyError, AttributeError):
|
|
return None
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=None)
|
|
|
|
def get_brport_unicast_flood(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BRPORT_UNICAST_FLOOD]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=0)
|
|
|
|
def get_brport_multicast_flood(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BRPORT_MCAST_FLOOD]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=0)
|
|
|
|
def get_brport_broadcast_flood(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BRPORT_BCAST_FLOOD]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=0)
|
|
|
|
def get_brport_neigh_suppress(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BRPORT_NEIGH_SUPPRESS]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=0)
|
|
|
|
def get_brport_learning(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_DATA][Link.IFLA_BRPORT_LEARNING]
|
|
except (KeyError, AttributeError):
|
|
return 0
|
|
|
|
def get_vlan_vni(self, ifname):
|
|
with self._cache_lock:
|
|
return self._bridge_vlan_vni_cache.get(ifname)
|
|
|
|
def get_pvid_and_vids(self, ifname):
|
|
"""
|
|
vlan-identifiers are stored in:
|
|
|
|
self._bridge_vlan_cache = {
|
|
ifname: [(vlan, flag), (vlan, flag), ...]
|
|
}
|
|
|
|
Those vlans are stored in compressed format (RTEXT_FILTER_BRVLAN_COMPRESSED)
|
|
We only uncompress the vlan when the user request it.
|
|
|
|
:param ifname:
|
|
:return tuple: pvid, vids = int, [int, ]
|
|
"""
|
|
pvid = None
|
|
vlans = []
|
|
try:
|
|
range_begin_vlan_id = None
|
|
range_flag = None
|
|
|
|
with self._cache_lock:
|
|
bridge_vlans_tuples = self._bridge_vlan_cache.get(ifname)
|
|
|
|
if bridge_vlans_tuples:
|
|
for (vlan_id, vlan_flag) in sorted(bridge_vlans_tuples):
|
|
|
|
if vlan_flag & Link.BRIDGE_VLAN_INFO_PVID:
|
|
pvid = vlan_id
|
|
|
|
if vlan_flag & Link.BRIDGE_VLAN_INFO_RANGE_BEGIN:
|
|
range_begin_vlan_id = vlan_id
|
|
range_flag = vlan_flag
|
|
|
|
elif vlan_flag & Link.BRIDGE_VLAN_INFO_RANGE_END:
|
|
range_flag |= vlan_flag
|
|
|
|
if not range_begin_vlan_id:
|
|
log.warning("BRIDGE_VLAN_INFO_RANGE_END is %d but we never "
|
|
"saw a BRIDGE_VLAN_INFO_RANGE_BEGIN" % vlan_id)
|
|
range_begin_vlan_id = vlan_id
|
|
|
|
for x in range(range_begin_vlan_id, vlan_id + 1):
|
|
vlans.append(x)
|
|
|
|
range_begin_vlan_id = None
|
|
range_flag = None
|
|
|
|
else:
|
|
vlans.append(vlan_id)
|
|
except Exception:
|
|
log.exception("get_bridge_vids")
|
|
return pvid, vlans
|
|
|
|
def get_pvid(self, ifname):
|
|
"""
|
|
Get Port VLAN ID for device 'ifname'
|
|
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
pvid = None
|
|
try:
|
|
with self._cache_lock:
|
|
bridge_vlans_tuples = self._bridge_vlan_cache.get(ifname)
|
|
|
|
if bridge_vlans_tuples:
|
|
|
|
for (vlan_id, vlan_flag) in sorted(bridge_vlans_tuples):
|
|
|
|
if vlan_flag & Link.BRIDGE_VLAN_INFO_PVID:
|
|
return vlan_id
|
|
except Exception:
|
|
log.exception("get_pvid")
|
|
return pvid
|
|
|
|
def bridge_exists(self, ifname):
|
|
"""
|
|
Check if cached device is a bridge
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_KIND] == "bridge"
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=False)
|
|
|
|
def bridge_is_vlan_aware(self, ifname):
|
|
"""
|
|
Return IFLA_BR_VLAN_FILTERING value
|
|
:param ifname:
|
|
:return: boolean
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA][Link.IFLA_BR_VLAN_FILTERING]
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=False)
|
|
|
|
def link_is_bridge_port(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_KIND] == "bridge"
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=False)
|
|
|
|
def bridge_port_exists(self, bridge_name, brport_name):
|
|
try:
|
|
with self._cache_lock:
|
|
# we are assuming that bridge_name is a valid bridge?
|
|
return self._slaves_master[brport_name] == bridge_name
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, bridge_name, str(e), return_value=False)
|
|
|
|
def get_bridge_name_from_port(self, bridge_port_name):
|
|
bridge_name = self.get_master(bridge_port_name)
|
|
# now that we have the master's name we just need to double check
|
|
# if the master is really a bridge
|
|
return bridge_name if self.link_is_bridge(bridge_name) else None
|
|
|
|
#def is_link_slave_kind(self, ifname, _type):
|
|
# try:
|
|
# with self._cache_lock:
|
|
# return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_KIND] == _type
|
|
# except (KeyError, AttributeError):
|
|
# return False
|
|
|
|
########################################
|
|
|
|
def get_link_ipv6_addrgen_mode(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_AF_SPEC].value[socket.AF_INET6][Link.IFLA_INET6_ADDR_GEN_MODE]
|
|
except (KeyError, AttributeError):
|
|
# default to 0 (eui64)
|
|
return 0
|
|
except TypeError as e:
|
|
return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=0)
|
|
|
|
#####################################################
|
|
#####################################################
|
|
#####################################################
|
|
#####################################################
|
|
#####################################################
|
|
|
|
def add_link(self, link):
|
|
"""
|
|
Cache RTM_NEWLINK packet
|
|
:param link:
|
|
:return:
|
|
"""
|
|
ifindex = link.ifindex
|
|
|
|
try:
|
|
ifname = link.get_attribute_value(Link.IFLA_IFNAME).decode()
|
|
except AttributeError:
|
|
# ifname is already a string we don't need to decode it
|
|
ifname = link.get_attribute_value(Link.IFLA_IFNAME)
|
|
|
|
# check if this device is registered in the ignore list
|
|
with self._ignore_rtm_newlinkq_lock:
|
|
if ifname in self._ignore_rtm_newlinkq:
|
|
return
|
|
# check if this device is registered in the nomaster list:
|
|
# if so we need to remove IFLA_MASTER attribute (if it fails
|
|
# it means we've received the final notification and we should
|
|
# unregister the device from our list.
|
|
with self._rtm_newlink_nomasterq_lock:
|
|
if ifname in self._rtm_newlink_nomasterq:
|
|
try:
|
|
del link.attributes[Link.IFLA_MASTER]
|
|
except Exception:
|
|
self._rtm_newlink_nomasterq.remove(ifname)
|
|
|
|
# we need to check if the device was previously enslaved
|
|
# so we can update the _masters_and_slaves and _slaves_master
|
|
# dictionaries if the master has changed or was un-enslaved.
|
|
old_ifla_master = None
|
|
|
|
with self._cache_lock:
|
|
|
|
# do we have a wait event registered for RTM_NEWLINK this ifname
|
|
if self._wait_event and self._wait_event == (ifname, RTM_NEWLINK):
|
|
self._wait_event_alarm.set()
|
|
|
|
try:
|
|
ifla_master_attr = self._link_cache[ifname].attributes.get(Link.IFLA_MASTER)
|
|
|
|
if ifla_master_attr:
|
|
old_ifla_master = ifla_master_attr.get_pretty_value()
|
|
except KeyError:
|
|
# link is not present in the cache
|
|
pass
|
|
except AttributeError:
|
|
# if this code is ever reached, this is very concerning and
|
|
# should never happen as _link_cache should always contains
|
|
# nlpacket.NetlinkPacket... maybe have some extra handling
|
|
# here just in case?
|
|
pass
|
|
|
|
self._link_cache[ifname] = link
|
|
|
|
######################################################
|
|
# update helper dictionaries and handle link renamed #
|
|
######################################################
|
|
if ifindex:
|
|
# ifindex can be None for packet added on ACK, it means
|
|
# that we are caching the request packet and not the
|
|
# notification coming from the kernel. We can leave
|
|
# those data-strctures empty and rely on our try/excepts
|
|
# in get_ifname/get_ifindex/get_master to do the work.
|
|
|
|
self._ifindex_by_ifname[ifname] = ifindex
|
|
|
|
rename_detected = False
|
|
old_ifname_entry_for_ifindex = self._ifname_by_ifindex.get(ifindex)
|
|
|
|
if old_ifname_entry_for_ifindex and old_ifname_entry_for_ifindex != ifname:
|
|
# The ifindex was reused for a new interface before we got the
|
|
# RTM_DELLINK notification or the device using that ifindex was
|
|
# renamed. We need to update the cache accordingly.
|
|
rename_detected = True
|
|
|
|
self._ifname_by_ifindex[ifindex] = ifname
|
|
|
|
if rename_detected:
|
|
# in this case we detected a rename... It should'nt happen has we should get a RTM_DELLINK before that.
|
|
# if we still detect a rename the opti is to get rid of the stale value directly
|
|
try:
|
|
del self._ifindex_by_ifname[old_ifname_entry_for_ifindex]
|
|
except KeyError:
|
|
log.debug('update_helper_dicts: del _ifindex_by_ifname[%s]: KeyError ifname: %s'
|
|
% (old_ifname_entry_for_ifindex, old_ifname_entry_for_ifindex))
|
|
try:
|
|
del self._link_cache[old_ifname_entry_for_ifindex]
|
|
except KeyError:
|
|
log.debug('update_helper_dicts: del _link_cache[%s]: KeyError ifname: %s'
|
|
% (old_ifname_entry_for_ifindex, old_ifname_entry_for_ifindex))
|
|
######################################################
|
|
######################################################
|
|
|
|
link_ifla_master_attr = link.attributes.get(Link.IFLA_MASTER)
|
|
if link_ifla_master_attr:
|
|
link_ifla_master = link_ifla_master_attr.get_pretty_value()
|
|
else:
|
|
link_ifla_master = None
|
|
|
|
# if the link has a master we need to store it in an helper dictionary, where
|
|
# the key is the master ifla_ifname and the value is a list of slaves, example:
|
|
# _masters_slaves_dict = {
|
|
# 'bond0': ['swp21', 'swp42']
|
|
# }
|
|
# this will be useful in the case we need to iterate on all slaves of a specific link
|
|
|
|
if old_ifla_master:
|
|
if old_ifla_master != link_ifla_master:
|
|
# the link was previously enslaved but master is now unset on this device
|
|
# we need to reflect that on the _masters_and_slaves and _slaves_master dictionaries
|
|
try:
|
|
self.__unslave_nolock(slave=ifname)
|
|
except NetlinkCacheIfindexNotFoundError:
|
|
pass
|
|
else:
|
|
# the master status didn't change we can assume that our internal
|
|
# masters_slaves dictionary is up to date and return here
|
|
return
|
|
|
|
if not link_ifla_master:
|
|
return
|
|
|
|
master_ifname = self._ifname_by_ifindex.get(link_ifla_master)
|
|
|
|
if not master_ifname:
|
|
# in this case we have a link object with IFLA_MASTER set to a ifindex
|
|
# but this ifindex is not in our _ifname_by_ifindex dictionary thus it's
|
|
# not in the _link_cache yet. This situation may happen when getting the
|
|
# very first link dump. The kernel dumps device in the "ifindex" order.
|
|
#
|
|
# So let's say you have a box with 4 ports (lo, eth0, swp1, swp2), then
|
|
# manually create a bond (bond0) and enslave swp1 and swp2, bond0 will
|
|
# have ifindex 5 but when getting the link dump swp1 will be handled first
|
|
# so at this point the cache has no idea if ifindex 5 is valid or not.
|
|
# But since we've made it this far we can assume that this is probably a
|
|
# valid device and will use sysfs to confirm.
|
|
master_device_path = '/sys/class/net/%s/master' % ifname
|
|
|
|
if os.path.exists(master_device_path):
|
|
# this check is necessary because realpath doesn't return None on error
|
|
# it returns it's input argument...
|
|
# >>> os.path.realpath('/sys/class/net/device_not_found')
|
|
# '/sys/class/net/device_not_found'
|
|
# >>>
|
|
master_ifname = os.path.basename(os.path.realpath(master_device_path))
|
|
|
|
if master_ifname in self._masters_and_slaves:
|
|
slave_list = self._masters_and_slaves[master_ifname]
|
|
if ifname not in slave_list:
|
|
slave_list.append(ifname)
|
|
else:
|
|
self._masters_and_slaves[master_ifname] = [ifname]
|
|
|
|
self._slaves_master[ifname] = master_ifname
|
|
|
|
def update_link_info_data(self, ifname, ifla_info_data):
|
|
"""
|
|
Update specific IFLA_INFO_DATA attributes of an existing cached device
|
|
ignore all errors
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_DATA].update(ifla_info_data)
|
|
except Exception:
|
|
pass
|
|
|
|
def update_link_ifla_address(self, ifname, ifla_address_str, ifla_address_int):
|
|
try:
|
|
with self._cache_lock:
|
|
self._link_cache[ifname].attributes[Link.IFLA_ADDRESS].value = ifla_address_str
|
|
self._link_cache[ifname].attributes[Link.IFLA_ADDRESS].raw = ifla_address_int
|
|
except Exception:
|
|
pass
|
|
|
|
def add_bridge_vlan(self, msg):
|
|
"""
|
|
Process AF_BRIDGE family packets (AF_BRIDGE family should be check
|
|
before calling this function).
|
|
|
|
Extract VLAN_INFO (vlan id and flag) and store it in cache.
|
|
|
|
:param link:
|
|
:return:
|
|
"""
|
|
vlans_list = []
|
|
|
|
# Todo: acquire the lock only when really needed
|
|
with self._cache_lock:
|
|
ifla_af_spec = msg.get_attribute_value(Link.IFLA_AF_SPEC)
|
|
ifname = msg.get_attribute_value(Link.IFLA_IFNAME)
|
|
|
|
if not ifla_af_spec:
|
|
return
|
|
|
|
try:
|
|
# We need to check if this object is still in cache, after a bridge
|
|
# is removed we still receive AF_BRIDGE notifications for it's slave
|
|
# those notifications should be ignored.
|
|
ifla_master = msg.get_attribute_value(Link.IFLA_MASTER)
|
|
|
|
if not ifla_master or not ifla_master in self._ifname_by_ifindex:
|
|
return
|
|
except Exception:
|
|
pass
|
|
|
|
# Example IFLA_AF_SPEC
|
|
# 20: 0x1c001a00 .... Length 0x001c (28), Type 0x001a (26) IFLA_AF_SPEC
|
|
# 21: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
|
|
# 22: 0x00000a00 ....
|
|
# 23: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
|
|
# 24: 0x00001000 ....
|
|
# 25: 0x08000200 .... Nested Attribute - Length 0x0008 (8), Type 0x0002 (2) IFLA_BRIDGE_VLAN_INFO
|
|
# 26: 0x00001400 ....
|
|
for x_type, x_value in ifla_af_spec.items():
|
|
if x_type == Link.IFLA_BRIDGE_VLAN_INFO:
|
|
for vlan_flag, vlan_id in x_value:
|
|
# We store these in the tuple as (vlan, flag) instead
|
|
# (flag, vlan) so that we can sort the list of tuples
|
|
vlans_list.append((vlan_id, vlan_flag))
|
|
|
|
elif x_type == Link.IFLA_BRIDGE_VLAN_TUNNEL_INFO:
|
|
self._bridge_vlan_vni_cache.update({ifname: x_value})
|
|
|
|
self._bridge_vlan_cache.update({ifname: vlans_list})
|
|
|
|
def force_add_slave(self, master, slave):
|
|
"""
|
|
When calling link_set_master, we don't want to wait for the RTM_GETLINK
|
|
notification - if the operation return with NL_SUCCESS we can manually
|
|
update our cache and move on
|
|
:param master:
|
|
:param slave:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
master_slaves = self._masters_and_slaves.get(master)
|
|
|
|
if not master_slaves:
|
|
self._masters_and_slaves[master] = [slave]
|
|
else:
|
|
if slave not in master_slaves:
|
|
master_slaves.append(slave)
|
|
|
|
# if the slave is already enslaved to another device we should
|
|
# make sure to remove it from the _masters_and_slaves data
|
|
# structure as well.
|
|
old_master = self._slaves_master.get(slave)
|
|
|
|
if old_master and old_master != master:
|
|
try:
|
|
self._masters_and_slaves.get(old_master, []).remove(slave)
|
|
except Exception:
|
|
pass
|
|
|
|
self._slaves_master[slave] = master
|
|
except Exception:
|
|
# since this is an optimization function we can ignore all error
|
|
pass
|
|
|
|
def force_add_slave_list(self, master, slave_list):
|
|
"""
|
|
When calling link_set_master, we don't want to wait for the RTM_GETLINK
|
|
notification - if the operation return with NL_SUCCESS we can manually
|
|
update our cache and move on
|
|
:param master:
|
|
:param slave:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
master_slaves = self._masters_and_slaves.get(master)
|
|
|
|
if not master_slaves:
|
|
self._masters_and_slaves[master] = slave_list
|
|
else:
|
|
for slave_ifname in slave_list:
|
|
if slave_ifname not in master_slaves:
|
|
master_slaves.append(slave_ifname)
|
|
|
|
for slave in slave_list:
|
|
self._slaves_master[slave] = master
|
|
except Exception:
|
|
# since this is an optimization function we can ignore all error
|
|
pass
|
|
|
|
def force_remove_link(self, ifname):
|
|
"""
|
|
When calling link_del (RTM_DELLINK) we need to manually remove the
|
|
associated cache entry - the RTM_DELLINK notification isn't received
|
|
instantaneously - we don't want to keep stale value in our cache
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
try:
|
|
ifindex = self.get_ifindex(ifname)
|
|
except (KeyError, NetlinkCacheIfnameNotFoundError):
|
|
ifindex = None
|
|
self.remove_link(None, link_ifname=ifname, link_ifindex=ifindex)
|
|
|
|
def remove_link(self, link, link_ifname=None, link_ifindex=None):
|
|
""" Process RTM_DELLINK packet and purge the cache accordingly """
|
|
if link:
|
|
ifindex = link.ifindex
|
|
ifname = link.get_attribute_value(Link.IFLA_IFNAME)
|
|
try:
|
|
# RTM_DELLINK received - we can now remove ifname from the
|
|
# ignore_rtm_newlinkq list. We don't bother checkin if the
|
|
# if name is present in the list (because it's likely in)
|
|
with self._ignore_rtm_newlinkq_lock:
|
|
self._ignore_rtm_newlinkq.remove(ifname)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
ifname = link_ifname
|
|
ifindex = link_ifindex
|
|
|
|
link_ifla_master = None
|
|
# when an enslaved device is removed we receive the RTM_DELLINK
|
|
# notification without the IFLA_MASTER attribute, we need to
|
|
# get the previous cached value in order to correctly update the
|
|
# _masters_and_slaves dictionary
|
|
|
|
with self._cache_lock:
|
|
try:
|
|
try:
|
|
ifla_master_attr = self._link_cache[ifname].attributes.get(Link.IFLA_MASTER)
|
|
if ifla_master_attr:
|
|
link_ifla_master = ifla_master_attr.get_pretty_value()
|
|
except KeyError:
|
|
# link is not present in the cache
|
|
pass
|
|
except AttributeError:
|
|
# if this code is ever reached this is very concerning and
|
|
# should never happen as _link_cache should always contains
|
|
# nlpacket.NetlinkPacket maybe have some extra handling here
|
|
# just in case?
|
|
pass
|
|
finally:
|
|
del self._link_cache[ifname]
|
|
except KeyError:
|
|
# KeyError means that the link doesn't exists in the cache
|
|
log.debug('del _link_cache: KeyError ifname: %s' % ifname)
|
|
|
|
try:
|
|
# like in __unslave_nolock() we need to make sure that all deleted link
|
|
# have their bridge-vlans and _slaves_master entries cleared.
|
|
for slave in list(self._masters_and_slaves[ifname]):
|
|
self.__unslave_nolock(slave, master=ifname)
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
del self._bridge_vlan_cache[ifname]
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
del self._bridge_vlan_vni_cache[ifname]
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
del self._ifname_by_ifindex[ifindex]
|
|
except KeyError:
|
|
log.debug('del _ifname_by_ifindex: KeyError ifindex: %s' % ifindex)
|
|
|
|
try:
|
|
del self._ifindex_by_ifname[ifname]
|
|
except KeyError:
|
|
log.debug('del _ifindex_by_ifname: KeyError ifname: %s' % ifname)
|
|
|
|
try:
|
|
del self._addr_cache[ifname]
|
|
except KeyError:
|
|
log.debug('del _addr_cache: KeyError ifname: %s' % ifname)
|
|
|
|
try:
|
|
del self._masters_and_slaves[ifname]
|
|
except KeyError:
|
|
log.debug('del _masters_and_slaves: KeyError ifname: %s' % ifname)
|
|
|
|
# if the device was enslaved to another device we need to remove
|
|
# it's entry from our _masters_and_slaves dictionary
|
|
if link_ifla_master and link_ifla_master > 0:
|
|
try:
|
|
self.__unslave_nolock(slave=ifname)
|
|
except NetlinkCacheIfindexNotFoundError as e:
|
|
log.debug('cache: remove_link: %s: %s' % (ifname, str(e)))
|
|
except KeyError:
|
|
log.debug('_masters_and_slaves[if%s].remove(%s): KeyError' % (link_ifla_master, ifname))
|
|
|
|
def _address_get_ifname_and_ifindex(self, addr):
|
|
ifindex = addr.ifindex
|
|
label = addr.get_attribute_value(Address.IFA_LABEL)
|
|
|
|
if not label:
|
|
try:
|
|
label = self.get_ifname(ifindex)
|
|
except NetlinkCacheIfindexNotFoundError:
|
|
pass
|
|
|
|
if isinstance(label, bytes):
|
|
label = label.decode()
|
|
|
|
return label, ifindex
|
|
|
|
@staticmethod
|
|
def __check_and_replace_address(address_list, new_addr):
|
|
"""
|
|
Check if new_addr is in address_list, if found we replace the occurrence
|
|
with the new and update object "new_addr"
|
|
|
|
address_list should be a valid list (check before calling to improve perf)
|
|
:param address_list:
|
|
:param new_addr:
|
|
:return:
|
|
"""
|
|
ip_with_prefix = new_addr.get_attribute_value(Address.IFA_ADDRESS)
|
|
|
|
for index, addr in enumerate(address_list):
|
|
if addr.get_attribute_value(Address.IFA_ADDRESS) == ip_with_prefix:
|
|
address_list[index] = new_addr
|
|
return True
|
|
|
|
return False
|
|
|
|
def add_address(self, addr):
|
|
ifname, ifindex = self._address_get_ifname_and_ifindex(addr)
|
|
|
|
if not ifname:
|
|
log.debug('nlcache: add_address: cannot cache addr for ifindex %s' % ifindex)
|
|
return
|
|
|
|
ip_version = addr.get_attribute_value(Address.IFA_ADDRESS).version
|
|
|
|
with self._cache_lock:
|
|
|
|
if ifname in self._addr_cache:
|
|
address_list = self._addr_cache[ifname][ip_version]
|
|
# First check if the address is already cached, if so
|
|
# we need to update it's entry with the new obj
|
|
if not address_list or not self.__check_and_replace_address(address_list, addr):
|
|
address_list.append(addr)
|
|
else:
|
|
self._addr_cache[ifname] = {
|
|
4: [],
|
|
6: [],
|
|
ip_version: [addr]
|
|
}
|
|
|
|
def force_address_flush_family(self, ifname, family):
|
|
try:
|
|
with self._cache_lock:
|
|
self._addr_cache[ifname][family] = []
|
|
except Exception:
|
|
pass
|
|
|
|
def address_flush_link(self, ifname):
|
|
"""
|
|
Flush address cache for link 'ifname'
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
self._addr_cache[ifname] = {4: [], 6: []}
|
|
except Exception:
|
|
pass
|
|
|
|
def force_remove_addr(self, ifname, addr):
|
|
"""
|
|
When calling addr_del (RTM_DELADDR) we need to manually remove the
|
|
associated cache entry - the RTM_DELADDR notification isn't received
|
|
instantaneously - we don't want to keep stale value in our cache
|
|
:param ifname:
|
|
:param addr:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
# iterate through the interface addresses
|
|
# to find which one to remove from the cache
|
|
obj_to_remove = None
|
|
|
|
for cache_addr in self._addr_cache[ifname][addr.version]:
|
|
try:
|
|
if cache_addr.attributes[Address.IFA_ADDRESS].value == addr:
|
|
obj_to_remove = cache_addr
|
|
except Exception:
|
|
try:
|
|
if cache_addr.attributes[Address.IFA_LOCAL].value == addr:
|
|
obj_to_remove = cache_addr
|
|
except Exception:
|
|
return
|
|
if obj_to_remove:
|
|
self._addr_cache[ifname][addr.version].remove(obj_to_remove)
|
|
except Exception:
|
|
pass
|
|
|
|
def remove_address(self, addr_to_remove):
|
|
ifname, _ = self._address_get_ifname_and_ifindex(addr_to_remove)
|
|
|
|
with self._cache_lock:
|
|
# iterate through the interface addresses
|
|
# to find which one to remove from the cache
|
|
try:
|
|
ip_version = addr_to_remove.get_attribute_value(Address.IFA_ADDRESS).version
|
|
except Exception:
|
|
try:
|
|
ip_version = addr_to_remove.get_attribute_value(Address.IFA_LOCAL).version
|
|
except Exception:
|
|
# print debug error
|
|
return
|
|
|
|
addrs_for_interface = self._addr_cache.get(ifname, {}).get(ip_version)
|
|
|
|
if not addrs_for_interface:
|
|
return
|
|
|
|
list_addr_to_remove = []
|
|
|
|
for addr in addrs_for_interface:
|
|
# compare each object attribute to see if they match
|
|
addr_match = False
|
|
|
|
for ifa_attr in self._ifa_attributes:
|
|
if addr.get_attribute_value(ifa_attr) != addr_to_remove.get_attribute_value(ifa_attr):
|
|
addr_match = False
|
|
break
|
|
addr_match = True
|
|
# if the address attribute matches we need to remove this one
|
|
|
|
if addr_match:
|
|
list_addr_to_remove.append(addr)
|
|
|
|
for addr in list_addr_to_remove:
|
|
try:
|
|
addrs_for_interface.remove(addr)
|
|
except ValueError as e:
|
|
log.debug('nlcache: remove_address: exception: %s' % e)
|
|
|
|
def get_ip_addresses(self, ifname: str) -> list:
|
|
addresses = []
|
|
try:
|
|
with self._cache_lock:
|
|
intf_addresses = self._addr_cache[ifname]
|
|
|
|
for addr in intf_addresses.get(4, []):
|
|
addresses.append(addr.attributes[Address.IFA_ADDRESS].value)
|
|
|
|
for addr in intf_addresses.get(6, []):
|
|
addresses.append(addr.attributes[Address.IFA_ADDRESS].value)
|
|
|
|
return addresses
|
|
except (KeyError, AttributeError):
|
|
return addresses
|
|
|
|
def link_has_ip(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
intf_addresses = self._addr_cache[ifname]
|
|
return bool(intf_addresses.get(4, None) or intf_addresses.get(6, None))
|
|
except Exception:
|
|
return False
|
|
|
|
############################################################################
|
|
############################################################################
|
|
############################################################################
|
|
|
|
def add_netconf(self, msg):
|
|
"""
|
|
cache RTM_NEWNETCONF objects
|
|
{
|
|
family: {
|
|
ifindex: RTM_NEWNETCONF
|
|
}
|
|
}
|
|
we currently only support AF_INET, AF_INET6 and AF_MPLS family.
|
|
"""
|
|
try:
|
|
with self._netconf_cache_lock:
|
|
self._netconf_cache[msg.family][msg.get_attribute_value(msg.NETCONFA_IFINDEX)] = msg
|
|
except Exception:
|
|
pass
|
|
|
|
def remove_netconf(self, msg):
|
|
"""
|
|
Process RTM_DELNETCONF, remove associated entry in our _netconf_cache
|
|
"""
|
|
try:
|
|
with self._netconf_cache_lock:
|
|
del self._netconf_cache[msg.family][msg.get_attribute_value(msg.NETCONFA_IFINDEX)]
|
|
except Exception:
|
|
pass
|
|
|
|
def get_netconf_forwarding(self, family, ifname):
|
|
"""
|
|
Return netconf device forwarding value
|
|
"""
|
|
try:
|
|
with self._netconf_cache_lock:
|
|
return self._netconf_cache[family][self.get_ifindex(ifname)].get_attribute_value(Netconf.NETCONFA_FORWARDING)
|
|
except Exception:
|
|
# if KeyError and family == AF_INET6: ipv6 is probably disabled on this device
|
|
return None
|
|
|
|
def get_netconf_mpls_input(self, ifname):
|
|
"""
|
|
Return netconf device MPLS input value
|
|
"""
|
|
try:
|
|
with self._netconf_cache_lock:
|
|
return self._netconf_cache[AF_MPLS][self.get_ifindex(ifname)].get_attribute_value(Netconf.NETCONFA_INPUT)
|
|
except Exception:
|
|
return None
|
|
|
|
############################################################################
|
|
############################################################################
|
|
############################################################################
|
|
|
|
@staticmethod
|
|
def get_user_configured_addresses(ifaceobj_list: list, with_address_virtual=False) -> list:
|
|
ip4 = []
|
|
ip6 = []
|
|
|
|
for ifaceobj in ifaceobj_list:
|
|
addresses = ifaceobj.get_attr_value("address")
|
|
|
|
if addresses:
|
|
for addr_index, addr in enumerate(addresses):
|
|
if "/" in addr:
|
|
ip_network_obj = ipnetwork.IPNetwork(addr)
|
|
else:
|
|
# if netmask is specified under the stanza we need to use to
|
|
# create the IPNetwork objects, otherwise let IPNetwork figure
|
|
# out the correct netmask for ip4 & ip6
|
|
ip_network_obj = ipnetwork.IPNetwork(addr, ifaceobj.get_attr_value_n("netmask", addr_index))
|
|
|
|
if ip_network_obj.version == 6:
|
|
ip6.append(ip_network_obj)
|
|
else:
|
|
ip4.append(ip_network_obj)
|
|
|
|
if not with_address_virtual:
|
|
continue
|
|
#
|
|
# address-virtual and vrrp ips also needs to be accounted for
|
|
#
|
|
addresses_virtual = ifaceobj.get_attr_value("address-virtual")
|
|
vrrp = ifaceobj.get_attr_value("vrrp")
|
|
|
|
for attr_config in (addresses_virtual, vrrp):
|
|
for addr_virtual_entry in attr_config or []:
|
|
for addr in addr_virtual_entry.split():
|
|
try:
|
|
ip_network_obj = ipnetwork.IPNetwork(addr)
|
|
|
|
if ip_network_obj.version == 6:
|
|
ip6.append(ip_network_obj)
|
|
else:
|
|
ip4.append(ip_network_obj)
|
|
except Exception:
|
|
continue
|
|
|
|
# always return ip4 first, followed by ip6
|
|
return ip4 + ip6
|
|
|
|
def get_managed_ip_addresses(self, ifname: str, ifaceobj_list: list, with_address_virtual: bool = False):
|
|
"""
|
|
Get all ip addresses managed by ifupdown2. As a network manager we need
|
|
to be able to detect manually added ip addresses (with iproute2 for
|
|
example).
|
|
We only addressed added by the kernel (scope: link).
|
|
|
|
Args:
|
|
ifname: str
|
|
ifaceobj_list: list of ifaceobj
|
|
with_address_virtual: boolean (to include vrrp and address-virtual)
|
|
|
|
Returns: List of ipnetwork.IPNetwork objects
|
|
"""
|
|
config_addrs = set(
|
|
self.get_user_configured_addresses(
|
|
ifaceobj_list,
|
|
with_address_virtual=with_address_virtual,
|
|
)
|
|
)
|
|
|
|
previous_state_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifname)
|
|
|
|
if previous_state_ifaceobjs:
|
|
for previous_state_addr in self.get_user_configured_addresses(
|
|
previous_state_ifaceobjs,
|
|
with_address_virtual=with_address_virtual,
|
|
):
|
|
config_addrs.add(previous_state_addr)
|
|
|
|
managed_addresses = []
|
|
|
|
for addr in self.get_ip_addresses(ifname):
|
|
if addr in config_addrs:
|
|
managed_addresses.append(addr)
|
|
|
|
elif not addr.scope & Route.RT_SCOPE_LINK:
|
|
managed_addresses.append(addr)
|
|
|
|
return managed_addresses
|
|
|
|
############################################################################
|
|
############################################################################
|
|
############################################################################
|
|
|
|
def addr_is_cached(self, ifname, addr):
|
|
"""
|
|
return True if addr is in cache
|
|
|
|
We might need to check if metric/peer and other attribute are also correctly cached.
|
|
We might also need to add a "force" attribute to skip the cache check
|
|
:param ifname:
|
|
:param ifindex:
|
|
:return:
|
|
"""
|
|
try:
|
|
with self._cache_lock:
|
|
for cache_addr in self._addr_cache[ifname][addr.version]:
|
|
try:
|
|
ifa_address = cache_addr.attributes[Address.IFA_ADDRESS].value
|
|
if ifa_address.ip == addr.ip and ifa_address.prefixlen == addr.prefixlen:
|
|
return True
|
|
except Exception:
|
|
try:
|
|
ifa_local = cache_addr.attributes[Address.IFA_LOCAL].value
|
|
return ifa_local.ip == addr.ip and ifa_local.prefixlen == addr.prefixlen
|
|
except Exception:
|
|
pass
|
|
except (KeyError, AttributeError):
|
|
pass
|
|
return False
|
|
|
|
# old
|
|
|
|
def get_link_obj(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname]
|
|
except KeyError:
|
|
return None
|
|
|
|
def get_link_info_slave_data(self, ifname):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_DATA]
|
|
except (KeyError, AttributeError):
|
|
return {}
|
|
|
|
def is_link_kind(self, ifname, _type):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_KIND] == _type
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
|
|
def is_link_slave_kind(self, ifname, _type):
|
|
try:
|
|
with self._cache_lock:
|
|
return self._link_cache[ifname].attributes[Link.IFLA_LINKINFO].value[Link.IFLA_INFO_SLAVE_KIND] == _type
|
|
except (KeyError, AttributeError):
|
|
return False
|
|
|
|
|
|
class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject):
|
|
|
|
__instance = None
|
|
VXLAN_UDP_PORT = 4789
|
|
|
|
@staticmethod
|
|
def init(log_level):
|
|
"""
|
|
Create the singleton via this init function
|
|
Following calls should use get_instance()
|
|
:param log_level:
|
|
:return:
|
|
"""
|
|
if not NetlinkListenerWithCache.__instance:
|
|
try:
|
|
NetlinkListenerWithCache.__instance = NetlinkListenerWithCache(log_level=log_level)
|
|
except Exception as e:
|
|
log.error('NetlinkListenerWithCache: init: %s' % e)
|
|
traceback.print_exc()
|
|
|
|
@staticmethod
|
|
def get_instance():
|
|
"""
|
|
Use this function to retrieve the active reference to the
|
|
NetlinkListenerWithCache, make sure you called .init() first
|
|
:return:
|
|
"""
|
|
if not NetlinkListenerWithCache.__instance:
|
|
raise NetlinkListenerWithCacheErrorNotInitialized("NetlinkListenerWithCache not initialized")
|
|
return NetlinkListenerWithCache.__instance
|
|
|
|
@staticmethod
|
|
def is_init():
|
|
return bool(NetlinkListenerWithCache.__instance)
|
|
|
|
def __init__(self, log_level):
|
|
"""
|
|
|
|
:param log_level:
|
|
"""
|
|
if NetlinkListenerWithCache.__instance:
|
|
raise RuntimeError("NetlinkListenerWithCache: invalid access. Please use NetlinkListenerWithCache.getInstance()")
|
|
else:
|
|
NetlinkListenerWithCache.__instance = self
|
|
|
|
nllistener.NetlinkManagerWithListener.__init__(
|
|
self,
|
|
groups=(
|
|
nlpacket.RTMGRP_LINK
|
|
| nlpacket.RTMGRP_IPV4_IFADDR
|
|
| nlpacket.RTMGRP_IPV6_IFADDR
|
|
| nlpacket.RTNLGRP_IPV4_NETCONF
|
|
| nlpacket.RTNLGRP_IPV6_NETCONF
|
|
| nlpacket.RTNLGRP_MPLS_NETCONF
|
|
),
|
|
start_listener=False,
|
|
error_notification=True
|
|
)
|
|
|
|
BaseObject.__init__(self)
|
|
|
|
signal.signal(signal.SIGTERM, self.signal_term_handler)
|
|
signal.signal(signal.SIGINT, self.signal_int_handler)
|
|
|
|
self.cache = _NetlinkCache()
|
|
|
|
# set specific log level to lower-level API
|
|
nllistener.log.setLevel(log_level)
|
|
nlpacket.log.setLevel(log_level)
|
|
nlmanager.log.setLevel(log_level)
|
|
|
|
self.IPNetwork_version_to_family = {4: socket.AF_INET, 6: socket.AF_INET6}
|
|
|
|
nlpacket.mac_int_to_str = lambda mac_int: ':'.join(('%012x' % mac_int)[i:i + 2] for i in range(0, 12, 2))
|
|
# Override the nlmanager's mac_int_to_str function
|
|
# Return an integer in MAC string format: xx:xx:xx:xx:xx:xx instead of xxxx.xxxx.xxxx
|
|
|
|
self.workq_handler = {
|
|
self.WORKQ_SERVICE_NETLINK_QUEUE: self.service_netlinkq,
|
|
}
|
|
|
|
# NetlinkListenerWithCache first dumps links and addresses then start
|
|
# a worker thread before returning. The worker thread processes the
|
|
# workq mainly to service (process) the netlinkq which contains our
|
|
# netlink packet (notification coming from the Kernel).
|
|
# When the main thread is making netlin requests (i.e. bridge add etc
|
|
# ...) the main thread will sleep (thread.event.wait) until we notify
|
|
# it when receiving an ack associated with the request. The request
|
|
# may fail and the kernel won't return an ACK but instead return a
|
|
# NLMSG_ERROR packet. We need to store those packet separatly because:
|
|
# - we could have several NLMSG_ERROR for different requests from
|
|
# different threads (in a multi-threaded ifupdown2 case)
|
|
# - we want to decode the packet and tell the user/log or even raise
|
|
# an exception with the appropriate message.
|
|
# User must check the return value of it's netlink requests and
|
|
# catch any exceptions, for that purpose please use API:
|
|
# - tx_nlpacket_get_response_with_error
|
|
self.errorq = list()
|
|
self.errorq_lock = threading.Lock()
|
|
self.errorq_enabled = True
|
|
|
|
# when ifupdown2 starts, we need to fill the netlink cache
|
|
# GET_LINK/ADDR request are asynchronous, we need to block
|
|
# and wait for the cache to be filled. We are using this one
|
|
# time netlinkq_notify_event to wait for the cache completion event
|
|
self.netlinkq_notify_event = None
|
|
|
|
# another threading event to make sure that the netlinkq worker thread is ready
|
|
self.is_ready = threading.Event()
|
|
|
|
self.worker = None
|
|
|
|
def __str__(self):
|
|
return "NetlinkListenerWithCache"
|
|
|
|
def start(self):
|
|
"""
|
|
Start NetlinkListener -
|
|
cache all links, bridges, addresses and netconfs
|
|
:return:
|
|
"""
|
|
self.restart_listener()
|
|
|
|
# set ifupdown2 specific supported and ignore messages
|
|
self.listener.supported_messages = (
|
|
nlpacket.RTM_NEWLINK,
|
|
nlpacket.RTM_DELLINK,
|
|
nlpacket.RTM_NEWADDR,
|
|
nlpacket.RTM_DELADDR,
|
|
nlpacket.RTM_NEWNETCONF,
|
|
nlpacket.RTM_DELNETCONF
|
|
)
|
|
self.listener.ignore_messages = (
|
|
nlpacket.RTM_GETLINK,
|
|
nlpacket.RTM_GETADDR,
|
|
nlpacket.RTM_GETNEIGH,
|
|
nlpacket.RTM_GETROUTE,
|
|
nlpacket.RTM_GETQDISC,
|
|
nlpacket.RTM_NEWNEIGH,
|
|
nlpacket.RTM_DELNEIGH,
|
|
nlpacket.RTM_NEWROUTE,
|
|
nlpacket.RTM_DELROUTE,
|
|
nlpacket.RTM_DELNETCONF,
|
|
nlpacket.RTM_NEWQDISC,
|
|
nlpacket.RTM_DELQDISC,
|
|
nlpacket.RTM_GETQDISC,
|
|
nlpacket.NLMSG_ERROR, # should be in supported_messages ?
|
|
nlpacket.NLMSG_DONE # should be in supported_messages ?
|
|
)
|
|
|
|
# get all links and wait for the cache to be filled
|
|
self.get_all_links_wait_netlinkq()
|
|
|
|
# get all addresses and wait for cache to be filled
|
|
self.get_all_addresses_wait_netlinkq()
|
|
|
|
# get a netconf dump and wait for the cached to be filled
|
|
self.get_all_netconf_wait_netlinkq()
|
|
|
|
# TODO: on ifquery we shoudn't start any thread (including listener in NetlinkListener)
|
|
# only for standalone code.
|
|
#import sys
|
|
#for arg in sys.argv:
|
|
# if 'ifquery' in arg:
|
|
# self.worker = None
|
|
# return
|
|
|
|
# start the netlinkq worker thread
|
|
self.worker = threading.Thread(target=self.main, name='NetlinkListenerWithCache')
|
|
self.worker.start()
|
|
self.is_ready.wait()
|
|
|
|
def cleanup(self):
|
|
if not self.__instance:
|
|
return
|
|
|
|
# passing 0, 0 to the handler so it doesn't log.info
|
|
self.signal_term_handler(0, 0)
|
|
|
|
if self.worker:
|
|
self.worker.join()
|
|
|
|
def main(self):
|
|
self.is_ready.set()
|
|
|
|
# This loop has two jobs:
|
|
# - process items on our workq
|
|
# - process netlink messages on our netlinkq, messages are placed there via our NetlinkListener
|
|
try:
|
|
while True:
|
|
# Sleep until our alarm goes off...NetlinkListener will set the alarm once it
|
|
# has placed a NetlinkPacket object on our netlinkq. If someone places an item on
|
|
# our workq they should also set our alarm...if they don't it is not the end of
|
|
# the world as we will wake up in 1s anyway to check to see if our shutdown_event
|
|
# has been set.
|
|
self.alarm.wait(0.1)
|
|
# when ifupdown2 is not running we could change the timeout to 1 sec or more (daemon mode)
|
|
# the daemon can also put a hook (pyinotify) on /etc/network/interfaces
|
|
# if we detect changes to that file it probably means that ifupdown2 will be called very soon
|
|
# then we can scale up our ops (or maybe unpause some of them)
|
|
# lets study the use cases
|
|
self.alarm.clear()
|
|
if self.shutdown_event.is_set():
|
|
break
|
|
|
|
while not self.workq.empty():
|
|
(event, options) = self.workq.get()
|
|
|
|
if event == self.WORKQ_SERVICE_NETLINK_QUEUE:
|
|
self.service_netlinkq(self.netlinkq_notify_event)
|
|
elif event == self.WORKQ_SERVICE_ERROR:
|
|
self.logger.error('NetlinkListenerWithCache: WORKQ_SERVICE_ERROR')
|
|
else:
|
|
raise Exception("Unsupported workq event %s" % event)
|
|
except Exception:
|
|
raise
|
|
finally:
|
|
# il faut surement mettre un try/except autour de la boucle au dessus
|
|
# car s'il y a une exception on ne quitte pas le listener thread
|
|
self.listener.shutdown_event.set()
|
|
self.listener.join()
|
|
|
|
def reset_errorq(self):
|
|
with self.errorq_lock:
|
|
self.logger.debug("nlcache: reset errorq")
|
|
self.errorq = []
|
|
|
|
def rx_rtm_newaddr(self, rxed_addr_packet):
|
|
super(NetlinkListenerWithCache, self).rx_rtm_newaddr(rxed_addr_packet)
|
|
self.cache.add_address(rxed_addr_packet)
|
|
|
|
def rx_rtm_dellink(self, link):
|
|
# cache only supports AF_UNSPEC for now
|
|
if link.family != socket.AF_UNSPEC:
|
|
return
|
|
super(NetlinkListenerWithCache, self).rx_rtm_dellink(link)
|
|
self.cache.remove_link(link)
|
|
|
|
def rx_rtm_deladdr(self, addr):
|
|
super(NetlinkListenerWithCache, self).rx_rtm_deladdr(addr)
|
|
self.cache.remove_address(addr)
|
|
|
|
def rx_rtm_newlink(self, rxed_link_packet):
|
|
# cache only supports AF_UNSPEC for now
|
|
# we can modify the cache to support more family:
|
|
# cache {
|
|
# intf_name: {
|
|
# AF_UNSPEC: NetlinkObj,
|
|
# AF_BRIDGE: NetlinkObj
|
|
# },
|
|
# }
|
|
if rxed_link_packet.family != socket.AF_UNSPEC:
|
|
# special handling for AF_BRIDGE packets
|
|
if rxed_link_packet.family == socket.AF_BRIDGE:
|
|
self.cache.add_bridge_vlan(rxed_link_packet)
|
|
return
|
|
|
|
super(NetlinkListenerWithCache, self).rx_rtm_newlink(rxed_link_packet)
|
|
self.cache.add_link(rxed_link_packet)
|
|
|
|
def rx_rtm_newnetconf(self, msg):
|
|
super(NetlinkListenerWithCache, self).rx_rtm_newnetconf(msg)
|
|
self.cache.add_netconf(msg)
|
|
|
|
def rx_rtm_delnetconf(self, msg):
|
|
super(NetlinkListenerWithCache, self).rx_rtm_delnetconf(msg)
|
|
self.cache.remove_netconf(msg)
|
|
|
|
def tx_nlpacket_get_response_with_error(self, nl_packet):
|
|
"""
|
|
After getting an ACK we need to check if this ACK was in fact an
|
|
error (NLMSG_ERROR). This function go through the .errorq list to
|
|
find the error packet associated with our request.
|
|
If found, we process it and raise an exception with the appropriate
|
|
information/message.
|
|
|
|
:param nl_packet:
|
|
:return:
|
|
"""
|
|
self.tx_nlpacket_get_response(nl_packet)
|
|
|
|
error_packet = None
|
|
index = 0
|
|
|
|
with self.errorq_lock:
|
|
for error in self.errorq:
|
|
if error.seq == nl_packet.seq and error.pid == nl_packet.pid:
|
|
error_packet = error
|
|
break
|
|
index += 1
|
|
|
|
if error_packet:
|
|
del self.errorq[index]
|
|
|
|
if not error_packet:
|
|
return True
|
|
|
|
error_code = abs(error_packet.negative_errno)
|
|
|
|
if error_packet.msgtype == NLMSG_DONE or not error_code:
|
|
# code NLE_SUCCESS...this is an ACK
|
|
return True
|
|
|
|
if self.debug:
|
|
error_packet.dump()
|
|
|
|
try:
|
|
# os.strerror might raise ValueError
|
|
strerror = os.strerror(error_code)
|
|
|
|
if strerror:
|
|
error_str = "operation failed with '%s' (%s)" % (strerror, error_code)
|
|
else:
|
|
error_str = "operation failed with code %s" % error_code
|
|
|
|
except ValueError:
|
|
error_str = "operation failed with code %s" % error_code
|
|
|
|
raise Exception(error_str)
|
|
|
|
def tx_nlpacket_get_response_with_error_and_cache_on_ack(self, packet):
|
|
"""
|
|
TX packet and manually cache the object
|
|
"""
|
|
self.tx_nlpacket_get_response_with_error(packet)
|
|
# When creating a new link via netlink, we don't always wait for the kernel
|
|
# NEWLINK notification to be cached to continue. If our request is ACKed by
|
|
# the OS we assume that the link was successfully created. Since we aren't
|
|
# waiting for the kernel notification to continue we need to manually fill
|
|
# our cache with the packet we just TX'ed. Once the NEWLINK notification
|
|
# is received it will simply override the previous entry.
|
|
# We need to keep track of those manually cached packets. We set a private
|
|
# flag on the objects via the attribute priv_flags
|
|
packet.priv_flags |= NLM_F_REQUEST
|
|
try:
|
|
# we need to decode the service header so all the attribute are properly
|
|
# filled in the packet object that we are about to store in cache.
|
|
# i.e.: packet.flags shouldn't contain NLM_F_* values but IFF_* (in case of Link object)
|
|
# otherwise call to cache.link_is_up() will probably return True
|
|
packet.decode_service_header()
|
|
except Exception:
|
|
# we can ignore all errors
|
|
pass
|
|
|
|
# Then we can use our normal "add_link" API call to cache the packet
|
|
# and fill up our additional internal data structures.
|
|
self.cache.add_link(packet)
|
|
|
|
def tx_nlpacket_get_response_with_error_and_wait_for_cache(self, ifname, nl_packet):
|
|
"""
|
|
The netlink request are asynchronus, but sometimes the main thread/user
|
|
would like to wait until the result of the request is cached. To do so
|
|
a cache event for ifname and nl_packet.msgtype is registered. Then the
|
|
netlink packet is TXed, errors are checked then we sleep until the
|
|
cache event is set (or we reach the timeout). This allows us to reliably
|
|
make sure is up to date with newly created/removed devices or addresses.
|
|
:param nl_packet:
|
|
:return:
|
|
"""
|
|
wait_event_registered = self.cache.register_wait_event(ifname, nl_packet.msgtype)
|
|
|
|
try:
|
|
result = self.tx_nlpacket_get_response_with_error(nl_packet)
|
|
except Exception:
|
|
# an error was caught, we need to unregister the event and raise again
|
|
self.cache.unregister_wait_event()
|
|
raise
|
|
|
|
if wait_event_registered:
|
|
self.cache.wait_event()
|
|
|
|
return result
|
|
|
|
def get_all_links_wait_netlinkq(self):
|
|
self.logger.info("requesting link dump")
|
|
# create netlinkq notify event so we can wait until the links are cached
|
|
self.netlinkq_notify_event = threading.Event()
|
|
self.get_all_links()
|
|
# we also need a dump of all existing bridge vlans
|
|
self.get_all_br_links(compress_vlans=True)
|
|
# block until the netlinkq was serviced and cached
|
|
self.service_netlinkq(self.netlinkq_notify_event)
|
|
self.netlinkq_notify_event.wait()
|
|
self.netlinkq_notify_event.clear()
|
|
|
|
def get_all_addresses_wait_netlinkq(self):
|
|
self.logger.info("requesting address dump")
|
|
self.netlinkq_notify_event = threading.Event()
|
|
self.get_all_addresses()
|
|
# block until the netlinkq was serviced and cached
|
|
self.service_netlinkq(self.netlinkq_notify_event)
|
|
self.netlinkq_notify_event.wait()
|
|
self.netlinkq_notify_event.clear()
|
|
self.netlinkq_notify_event = False
|
|
|
|
def get_all_netconf_wait_netlinkq(self):
|
|
self.logger.info("requesting netconf dump")
|
|
self.netlinkq_notify_event = threading.Event()
|
|
self.netconf_dump()
|
|
# block until the netlinkq was serviced and cached
|
|
self.service_netlinkq(self.netlinkq_notify_event)
|
|
self.netlinkq_notify_event.wait()
|
|
self.netlinkq_notify_event.clear()
|
|
self.netlinkq_notify_event = False
|
|
|
|
def vlan_modify(self, msgtype, ifindex, vlanid_start, vlanid_end=None, bridge_self=False, bridge_master=False, pvid=False, untagged=False):
|
|
"""
|
|
iproute2 bridge/vlan.c vlan_modify()
|
|
"""
|
|
assert msgtype in (RTM_SETLINK, RTM_DELLINK), "Invalid msgtype %s, must be RTM_SETLINK or RTM_DELLINK" % msgtype
|
|
assert vlanid_start >= 1 and vlanid_start <= 4096, "Invalid VLAN start %s" % vlanid_start
|
|
|
|
if vlanid_end is None:
|
|
vlanid_end = vlanid_start
|
|
|
|
assert vlanid_end >= 1 and vlanid_end <= 4096, "Invalid VLAN end %s" % vlanid_end
|
|
assert vlanid_start <= vlanid_end, "Invalid VLAN range %s-%s, start must be <= end" % (vlanid_start, vlanid_end)
|
|
|
|
debug = msgtype in self.debug
|
|
bridge_flags = 0
|
|
vlan_info_flags = 0
|
|
|
|
link = Link(msgtype, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_BRIDGE, ifindex, 0, 0)
|
|
|
|
if bridge_self:
|
|
bridge_flags |= Link.BRIDGE_FLAGS_SELF
|
|
|
|
if bridge_master:
|
|
bridge_flags |= Link.BRIDGE_FLAGS_MASTER
|
|
|
|
if pvid:
|
|
vlan_info_flags |= Link.BRIDGE_VLAN_INFO_PVID
|
|
|
|
if untagged:
|
|
vlan_info_flags |= Link.BRIDGE_VLAN_INFO_UNTAGGED
|
|
|
|
ifla_af_spec = OrderedDict()
|
|
|
|
if bridge_flags:
|
|
ifla_af_spec[Link.IFLA_BRIDGE_FLAGS] = bridge_flags
|
|
|
|
# just one VLAN
|
|
if vlanid_start == vlanid_end:
|
|
ifla_af_spec[Link.IFLA_BRIDGE_VLAN_INFO] = [(vlan_info_flags, vlanid_start), ]
|
|
|
|
# a range of VLANs
|
|
else:
|
|
ifla_af_spec[Link.IFLA_BRIDGE_VLAN_INFO] = [
|
|
(vlan_info_flags | Link.BRIDGE_VLAN_INFO_RANGE_BEGIN, vlanid_start),
|
|
(vlan_info_flags | Link.BRIDGE_VLAN_INFO_RANGE_END, vlanid_end)
|
|
]
|
|
|
|
link.add_attribute(Link.IFLA_AF_SPEC, ifla_af_spec)
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error(link)
|
|
|
|
def vlan_set_bridge_binding(self, ifname, bridge_binding=True):
|
|
"""
|
|
Set VLAN_FLAG_BRIDGE_BINDING on vlan interface
|
|
:param ifname: the vlan interface
|
|
:param bridge_binding: True to set the flag, False to unset
|
|
"""
|
|
self.logger.info("%s: netlink: ip link set dev %s type vlan bridge_binding %s" % (ifname, ifname, "on" if bridge_binding else "off"))
|
|
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, 0, 0, 0)
|
|
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
info_data = {Link.IFLA_VLAN_FLAGS: {Link.VLAN_FLAG_BRIDGE_BINDING: bridge_binding}}
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "vlan",
|
|
Link.IFLA_INFO_DATA: info_data
|
|
})
|
|
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error(link)
|
|
|
|
#############################################################################################################
|
|
# Netlink API ###############################################################################################
|
|
#############################################################################################################
|
|
|
|
def link_add(self, ifname, kind):
|
|
self.link_add_with_attributes(ifname, kind, {})
|
|
|
|
def link_add_with_attributes(self, ifname, kind, ifla):
|
|
"""
|
|
Build and TX a RTM_NEWLINK message to add the desired interface
|
|
"""
|
|
if ifla:
|
|
self.logger.info("%s: netlink: ip link add dev %s type %s (with attributes)" % (ifname, ifname, kind))
|
|
self.logger.debug("attributes: %s" % ifla)
|
|
else:
|
|
self.logger.info("%s: netlink: ip link add dev %s type %s" % (ifname, ifname, kind))
|
|
try:
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
|
|
for nl_attr, value in list(ifla.items()):
|
|
link.add_attribute(nl_attr, value)
|
|
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: kind
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
except Exception as e:
|
|
raise Exception("%s: cannot create link %s type %s" % (ifname, ifname, kind))
|
|
|
|
def link_add_with_attributes_dry_run(self, ifname, kind, ifla):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link add dev %s type %s" % (ifname, kind))
|
|
self.logger.debug("attributes: %s" % ifla)
|
|
|
|
###
|
|
|
|
def __link_set_flag(self, ifname, flags):
|
|
"""
|
|
Bring interface 'ifname' up (raises on error)
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
try:
|
|
link = Link(RTM_NEWLINK, RTM_NEWLINK in self.debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack("=BxxxiLL", socket.AF_UNSPEC, 0, flags, Link.IFF_UP)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.build_message(next(self.sequence), self.pid)
|
|
result = self.tx_nlpacket_get_response_with_error(link)
|
|
# if we reach this code it means the operation went through
|
|
# without exception we can update the cache value this is
|
|
# needed for the following case (and probably others):
|
|
#
|
|
# ifdown bond0 ; ip link set dev bond_slave down
|
|
# ifup bond0
|
|
# at the beginning the slaves are admin down
|
|
# ifupdownmain:run_up link up the slave
|
|
# the bond addon check if the slave is up or down
|
|
# and admin down the slave before enslavement
|
|
# but the cache didn't process the UP notification yet
|
|
# so the cache has a stale value and we try to enslave
|
|
# a port, that is admin up, to a bond resulting
|
|
# in an unexpected failure
|
|
self.cache.override_link_flag(ifname, flags)
|
|
return result
|
|
except Exception as e:
|
|
raise NetlinkError(e, "ip link set dev %s %s" % (ifname, "up" if flags == Link.IFF_UP else "down"), ifname=ifname)
|
|
|
|
def link_up(self, ifname):
|
|
if not self.cache.link_is_up(ifname):
|
|
self.logger.info("%s: netlink: ip link set dev %s up" % (ifname, ifname))
|
|
self.__link_set_flag(ifname, flags=Link.IFF_UP)
|
|
|
|
def link_up_force(self, ifname):
|
|
self.logger.info("%s: netlink: ip link set dev %s up" % (ifname, ifname))
|
|
self.__link_set_flag(ifname, flags=Link.IFF_UP)
|
|
|
|
def link_down(self, ifname):
|
|
if self.cache.link_is_up(ifname):
|
|
self.logger.info("%s: netlink: ip link set dev %s down" % (ifname, ifname))
|
|
self.__link_set_flag(ifname, flags=0)
|
|
|
|
def link_down_force(self, ifname):
|
|
self.logger.info("%s: netlink: ip link set dev %s down" % (ifname, ifname))
|
|
self.__link_set_flag(ifname, flags=0)
|
|
|
|
def link_up_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s up" % ifname)
|
|
|
|
def link_down_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s down" % ifname)
|
|
|
|
def link_down_force_dry_run(self, ifname):
|
|
self.link_down_dry_run(ifname)
|
|
|
|
###
|
|
|
|
def __link_set_protodown(self, ifname, state):
|
|
debug = RTM_NEWLINK in self.debug
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack("=BxxxiLL", socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_PROTO_DOWN, state)
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error(link)
|
|
|
|
def link_set_protodown_on(self, ifname):
|
|
"""
|
|
Bring ifname up by setting IFLA_PROTO_DOWN on
|
|
"""
|
|
if self.cache.get_link_protodown(ifname) == 1:
|
|
return True
|
|
|
|
self.logger.info("%s: netlink: set link %s protodown on" % (ifname, ifname))
|
|
try:
|
|
self.__link_set_protodown(ifname, 1)
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot set link %s protodown on" % ifname, ifname=ifname)
|
|
|
|
def link_set_protodown_off(self, ifname):
|
|
"""
|
|
Take ifname down by setting IFLA_PROTO_DOWN off
|
|
"""
|
|
if self.cache.get_link_protodown(ifname) == 0:
|
|
return True
|
|
|
|
self.logger.info("%s: netlink: set link %s protodown off" % (ifname, ifname))
|
|
try:
|
|
self.__link_set_protodown(ifname, 0)
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot set link %s protodown off" % ifname, ifname=ifname)
|
|
|
|
def link_set_protodown_on_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: set link %s protodown on" % ifname)
|
|
|
|
def link_set_protodown_off_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: set link %s protodown off" % ifname)
|
|
|
|
###
|
|
|
|
def link_del(self, ifname):
|
|
"""
|
|
Send RTM_DELLINK request
|
|
:param ifname:
|
|
:return:
|
|
"""
|
|
try:
|
|
try:
|
|
ifindex = self.cache.get_ifindex(ifname)
|
|
except NetlinkCacheIfnameNotFoundError:
|
|
# link doesn't exists on the system
|
|
return True
|
|
|
|
self.logger.info("%s: netlink: ip link del %s" % (ifname, ifname))
|
|
|
|
debug = RTM_DELLINK in self.debug
|
|
|
|
link = Link(RTM_DELLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack("Bxxxiii", socket.AF_UNSPEC, ifindex, 0, 0)
|
|
link.build_message(next(self.sequence), self.pid)
|
|
|
|
try:
|
|
# We need to register this ifname so the cache can ignore and discard
|
|
# any further RTM_NEWLINK packet until we receive the associated
|
|
# RTM_DELLINK notification
|
|
self.cache.append_to_ignore_rtm_newlinkq(ifname)
|
|
|
|
result = self.tx_nlpacket_get_response_with_error(link)
|
|
|
|
# Manually purge the cache entry for ifname to make sure we don't have
|
|
# any stale value in our cache
|
|
self.cache.force_remove_link(ifname)
|
|
return result
|
|
except Exception:
|
|
# Something went wrong while sending the RTM_DELLINK request
|
|
# we need to clear ifname from the ignore_rtm_newlinkq list
|
|
self.cache.remove_from_ignore_rtm_newlinkq(ifname)
|
|
raise
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot delete link %s" % ifname, ifname=ifname)
|
|
|
|
def link_del_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link del %s" % ifname)
|
|
|
|
###
|
|
|
|
def __link_set_master(self, ifname, master_ifindex, master_ifname=None):
|
|
debug = RTM_NEWLINK in self.debug
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack("=BxxxiLL", socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_MASTER, master_ifindex)
|
|
link.build_message(next(self.sequence), self.pid)
|
|
result = self.tx_nlpacket_get_response_with_error(link)
|
|
# opti:
|
|
# if we reach this code it means the slave/unslave opreation went through
|
|
# we can manually update our cache to reflect the change without having
|
|
# to wait for the netlink notification
|
|
if master_ifindex:
|
|
self.cache.force_add_slave(master_ifname, ifname)
|
|
else:
|
|
self.cache.override_cache_unslave_link(slave=ifname, master=master_ifname)
|
|
return result
|
|
|
|
def link_set_master(self, ifname, master_ifname):
|
|
self.logger.info("%s: netlink: ip link set dev %s master %s" % (ifname, ifname, master_ifname))
|
|
try:
|
|
self.__link_set_master(ifname, self.cache.get_ifindex(master_ifname), master_ifname=master_ifname)
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot enslave link %s to %s" % (ifname, master_ifname), ifname=ifname)
|
|
|
|
def link_set_nomaster(self, ifname):
|
|
self.logger.info("%s: netlink: ip link set dev %s nomaster" % (ifname, ifname))
|
|
try:
|
|
self.cache.append_to_rtm_newlink_nomasterq(ifname)
|
|
self.__link_set_master(ifname, 0)
|
|
except Exception as e:
|
|
self.cache.remove_from_rtm_newlink_nomasterq(ifname)
|
|
raise NetlinkError(e, "cannot un-enslave link %s" % ifname, ifname=ifname)
|
|
|
|
def link_set_master_dry_run(self, ifname, master_dev):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s master %s" % (ifname, master_dev))
|
|
|
|
def link_set_nomaster_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s nomaster" % ifname)
|
|
|
|
###
|
|
|
|
def link_set_address_dry_run(self, ifname, hw_address, hw_address_int):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s address %s" % (ifname, hw_address))
|
|
|
|
def link_set_address(self, ifname, hw_address, hw_address_int):
|
|
is_link_up = self.cache.link_is_up(ifname)
|
|
# check if the link is already up or not if the link is
|
|
# up we need to down it then make sure we up it again
|
|
try:
|
|
if is_link_up:
|
|
self.link_down_force(ifname)
|
|
|
|
self.logger.info("%s: netlink: ip link set dev %s address %s" % (ifname, ifname, hw_address))
|
|
debug = RTM_NEWLINK in self.debug
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_ADDRESS, hw_address)
|
|
|
|
link.build_message(next(self.sequence), self.pid)
|
|
result = self.tx_nlpacket_get_response_with_error(link)
|
|
|
|
# if we reach that code it means we got an ACK from the kernel, we can pro-actively
|
|
# update our local cache to reflect the change until the notificate arrives
|
|
self.cache.update_link_ifla_address(ifname, hw_address, hw_address_int)
|
|
|
|
return result
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot set dev %s address %s" % (ifname, hw_address), ifname=ifname)
|
|
finally:
|
|
if is_link_up:
|
|
self.link_up_force(ifname)
|
|
|
|
###
|
|
|
|
__macvlan_mode = {
|
|
"private": Link.MACVLAN_MODE_PRIVATE,
|
|
"vepa": Link.MACVLAN_MODE_VEPA,
|
|
"bridge": Link.MACVLAN_MODE_BRIDGE,
|
|
"passthru": Link.MACVLAN_MODE_PASSTHRU,
|
|
"source": Link.MACVLAN_MODE_SOURCE
|
|
}
|
|
|
|
def link_add_macvlan(self, ifname, macvlan_ifname, macvlan_mode=None):
|
|
self.logger.info(
|
|
"%s: netlink: ip link add link %s name %s type macvlan mode %s"
|
|
% (ifname, ifname, macvlan_ifname, macvlan_mode if macvlan_mode else "private")
|
|
)
|
|
try:
|
|
ifindex = self.cache.get_ifindex(ifname)
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack("Bxxxiii", socket.AF_UNSPEC, 0, 0, 0)
|
|
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
|
|
if ifindex:
|
|
link.add_attribute(Link.IFLA_LINK, ifindex)
|
|
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "macvlan",
|
|
Link.IFLA_INFO_DATA: {
|
|
Link.IFLA_MACVLAN_MODE: self.__macvlan_mode.get(
|
|
macvlan_mode,
|
|
Link.MACVLAN_MODE_PRIVATE
|
|
)
|
|
}
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
|
|
except Exception as e:
|
|
raise Exception(
|
|
"netlink: %s: cannot create macvlan %s: %s"
|
|
% (ifname, macvlan_ifname, str(e))
|
|
)
|
|
|
|
def link_add_macvlan_dry_run(self, ifname, macvlan_ifame, macvlan_mode=None):
|
|
self.log_info_ifname_dry_run(
|
|
ifname,
|
|
"netlink: ip link add link %s name %s type macvlan mode %s"
|
|
% (ifname, macvlan_ifame, macvlan_mode if macvlan_mode else "private")
|
|
)
|
|
return True
|
|
|
|
###
|
|
|
|
def link_add_vrf(self, ifname, vrf_table):
|
|
self.logger.info("%s: netlink: ip link add dev %s type vrf table %s" % (ifname, ifname, vrf_table))
|
|
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "vrf",
|
|
Link.IFLA_INFO_DATA: {
|
|
Link.IFLA_VRF_TABLE: int(vrf_table)
|
|
}
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
|
|
def link_add_vrf_dry_run(self, ifname, vrf_table):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link add dev %s type vrf table %s" % (ifname, vrf_table))
|
|
return True
|
|
|
|
###
|
|
|
|
def link_add_bridge(self, ifname, mtu=None):
|
|
self.logger.info("%s: netlink: ip link add dev %s type bridge" % (ifname, ifname))
|
|
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
|
|
if mtu:
|
|
self.logger.info("%s: netlink: set bridge mtu %s" % (ifname, mtu))
|
|
link.add_attribute(Link.IFLA_MTU, mtu)
|
|
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "bridge",
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
|
|
def link_add_bridge_dry_run(self, ifname):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link add dev %s type bridge" % ifname)
|
|
return True
|
|
|
|
def link_set_bridge_info_data(self, ifname, ifla_info_data):
|
|
self.logger.info(
|
|
"%s: netlink: ip link set dev %s type bridge (with attributes)"
|
|
% (ifname, ifname)
|
|
)
|
|
self.logger.debug("attributes: %s" % ifla_info_data)
|
|
|
|
try:
|
|
debug = RTM_NEWLINK in self.debug
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "bridge",
|
|
Link.IFLA_INFO_DATA: ifla_info_data
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
result = self.tx_nlpacket_get_response_with_error(link)
|
|
|
|
self.cache.update_link_info_data(ifname, ifla_info_data)
|
|
|
|
return result
|
|
except Exception as e:
|
|
raise Exception("%s: netlink: cannot create bridge or set attributes: %s" % (ifname, str(e)))
|
|
|
|
def link_set_bridge_info_data_dry_run(self, ifname, ifla_info_data):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link add dev %s type bridge (with attributes)" % ifname)
|
|
self.logger.debug("attributes: %s" % ifla_info_data)
|
|
|
|
###
|
|
|
|
def link_add_bridge_vlan(self, ifname, vlan_id):
|
|
"""
|
|
Add VLAN(s) to a bridge interface
|
|
"""
|
|
self.logger.info("%s: netlink: bridge vlan add vid %s dev %s" % (ifname, vlan_id, ifname))
|
|
try:
|
|
ifindex = self.cache.get_ifindex(ifname)
|
|
self.vlan_modify(RTM_SETLINK, ifindex, vlan_id, bridge_self=True)
|
|
# TODO: we should probably fill our internal cache when when the ACK is received.
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot add bridge vlan %s" % vlan_id, ifname=ifname)
|
|
|
|
def link_del_bridge_vlan(self, ifname, vlan_id):
|
|
"""
|
|
Delete VLAN(s) from a bridge interface
|
|
"""
|
|
self.logger.info("%s: netlink: bridge vlan del vid %s dev %s" % (ifname, vlan_id, ifname))
|
|
try:
|
|
ifindex = self.cache.get_ifindex(ifname)
|
|
self.vlan_modify(RTM_DELLINK, ifindex, vlan_id, bridge_self=True)
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot remove bridge vlan %s" % vlan_id, ifname=ifname)
|
|
|
|
def link_add_bridge_vlan_dry_run(self, ifname, vlan_id):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: bridge vlan add vid %s dev %s" % (vlan_id, ifname))
|
|
|
|
def link_del_bridge_vlan_dry_run(self, ifname, vlan_id):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: bridge vlan del vid %s dev %s" % (vlan_id, ifname))
|
|
|
|
###
|
|
|
|
def link_add_vlan(self, vlan_raw_device, ifname, vlan_id, vlan_protocol=None, bridge_binding=None):
|
|
"""
|
|
ifindex is the index of the parent interface that this sub-interface
|
|
is being added to
|
|
|
|
If you name an interface swp2.17 but assign it to vlan 12, the kernel
|
|
will return a very misleading NLE_MSG_OVERFLOW error. It only does
|
|
this check if the ifname uses dot notation.
|
|
|
|
Do this check here so we can provide a more intuitive error
|
|
"""
|
|
vlan_iproute2_cmd = ["ip link add link %s name %s type vlan id %s" % (vlan_raw_device, ifname, vlan_id)]
|
|
try:
|
|
ifla_info_data = {Link.IFLA_VLAN_ID: vlan_id}
|
|
|
|
if vlan_protocol:
|
|
vlan_iproute2_cmd.append("protocol %s" % vlan_protocol)
|
|
ifla_info_data[Link.IFLA_VLAN_PROTOCOL] = vlan_protocol
|
|
|
|
bridge_binding_str = ""
|
|
|
|
if bridge_binding is not None:
|
|
bridge_binding_str = "bridge_binding %s" % ("on" if bridge_binding else "off")
|
|
ifla_info_data[Link.IFLA_VLAN_FLAGS] = {Link.VLAN_FLAG_BRIDGE_BINDING: bridge_binding}
|
|
|
|
self.logger.info("%s: netlink: %s %s" % (ifname, " ".join(vlan_iproute2_cmd), bridge_binding_str))
|
|
|
|
if "." in ifname:
|
|
ifname_vlanid = int(ifname.split(".")[-1])
|
|
|
|
if ifname_vlanid != vlan_id:
|
|
raise Exception(
|
|
"Interface %s must belong to VLAN %d (VLAN %d was requested)"
|
|
% (ifname, ifname_vlanid, vlan_id)
|
|
)
|
|
|
|
ifindex = self.cache.get_ifindex(vlan_raw_device)
|
|
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_LINK, ifindex)
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "vlan",
|
|
Link.IFLA_INFO_DATA: ifla_info_data
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
except Exception as e:
|
|
if "Invalid argument" in str(e) and bridge_binding is not None:
|
|
raise RetryCMD(cmd=" ".join(vlan_iproute2_cmd))
|
|
else:
|
|
raise NetlinkError(e, "cannot create vlan %s %s" % (ifname, vlan_id), ifname=ifname)
|
|
|
|
def link_add_vlan_dry_run(self, vlan_raw_device, ifname, vlan_id, vlan_protocol=None):
|
|
"""
|
|
ifindex is the index of the parent interface that this sub-interface
|
|
is being added to
|
|
|
|
If you name an interface swp2.17 but assign it to vlan 12, the kernel
|
|
will return a very misleading NLE_MSG_OVERFLOW error. It only does
|
|
this check if the ifname uses dot notation.
|
|
|
|
Do this check here so we can provide a more intuitive error
|
|
"""
|
|
if vlan_protocol:
|
|
self.log_info_ifname_dry_run(
|
|
ifname,
|
|
"netlink: ip link add link %s name %s type vlan id %s protocol %s"
|
|
% (vlan_raw_device, ifname, vlan_id, vlan_protocol)
|
|
)
|
|
|
|
else:
|
|
self.log_info_ifname_dry_run(
|
|
ifname,
|
|
"netlink: ip link add link %s name %s type vlan id %s"
|
|
% (vlan_raw_device, ifname, vlan_id)
|
|
)
|
|
|
|
###
|
|
|
|
def link_add_vxlan_with_info_data(self, ifname, info_data):
|
|
"""
|
|
cmd = ["ip link add %s type vxlan id %s" % (ifname, id)]
|
|
|
|
if port:
|
|
cmd.append("dstport %s" % port)
|
|
info_data[nlpacket.Link.IFLA_VXLAN_PORT] = int(port)
|
|
|
|
if local:
|
|
cmd.append("local %s" % local)
|
|
info_data[nlpacket.Link.IFLA_VXLAN_LOCAL] = local
|
|
|
|
if ageing:
|
|
cmd.append("ageing %s" % ageing)
|
|
info_data[nlpacket.Link.IFLA_VXLAN_AGEING] = int(ageing)
|
|
|
|
if group:
|
|
if group.ip.is_multicast:
|
|
cmd.append("group %s" % group)
|
|
else:
|
|
cmd.append("remote %s" % group)
|
|
info_data[nlpacket.Link.IFLA_VXLAN_GROUP] = group
|
|
else:
|
|
cmd.append("noremote")
|
|
|
|
if not learning:
|
|
cmd.append("nolearning")
|
|
info_data[nlpacket.Link.IFLA_VXLAN_LEARNING] = int(learning)
|
|
|
|
if not udp_csum:
|
|
cmd.append("noudpcsum")
|
|
info_data[nlpacket.Link.IFLA_VXLAN_UDP_CSUM] = int(udp_csum)
|
|
|
|
if physdev:
|
|
cmd.append("dev %s" % physdev)
|
|
info_data[nlpacket.Link.IFLA_VXLAN_LINK] = self.cache.get_ifindex(physdev)
|
|
|
|
if ttl:
|
|
cmd.append("ttl %s" % ttl)
|
|
info_data[nlpacket.Link.IFLA_VXLAN_TTL] = ttl
|
|
|
|
self.logger.info('%s: netlink: %s' % (ifname, " ".join(cmd)))
|
|
|
|
:param ifname:
|
|
:param info_data:
|
|
:return:
|
|
"""
|
|
self.logger.info(
|
|
"%s: netlink: ip link add dev %s type vxlan id %s (with attributes)"
|
|
% (ifname, ifname, info_data.get(Link.IFLA_VXLAN_ID))
|
|
)
|
|
self.logger.debug("attributes: %s" % info_data)
|
|
|
|
debug = RTM_NEWLINK in self.debug
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "vxlan",
|
|
Link.IFLA_INFO_DATA: info_data
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
|
|
def link_add_vxlan_with_info_data_dry_run(self, ifname, info_data):
|
|
self.log_info_ifname_dry_run(
|
|
ifname,
|
|
"netlink: ip link add dev %s type vxlan id %s (with attributes)"
|
|
% (ifname, info_data.get(Link.IFLA_VXLAN_ID))
|
|
)
|
|
self.logger.debug("attributes: %s" % info_data)
|
|
return True
|
|
|
|
###
|
|
|
|
def link_add_bond_with_info_data(self, ifname, ifla_info_data):
|
|
self.logger.info(
|
|
"%s: netlink: ip link add dev %s type bond (with attributes)"
|
|
% (ifname, ifname)
|
|
)
|
|
self.logger.debug("attributes: %s" % ifla_info_data)
|
|
|
|
try:
|
|
debug = RTM_NEWLINK in self.debug
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
link.add_attribute(Link.IFLA_LINKINFO, {
|
|
Link.IFLA_INFO_KIND: "bond",
|
|
Link.IFLA_INFO_DATA: ifla_info_data
|
|
})
|
|
link.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error_and_cache_on_ack(link)
|
|
except Exception as e:
|
|
raise Exception("%s: netlink: cannot create bond with attributes: %s" % (ifname, str(e)))
|
|
|
|
def link_add_bond_with_info_data_dry_run(self, ifname, ifla_info_data):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link add dev %s type bond (with attributes)" % ifname)
|
|
self.logger.debug("attributes: %s" % ifla_info_data)
|
|
|
|
###
|
|
|
|
def link_set_brport_with_info_slave_data(self, ifname, kind, ifla_info_data, ifla_info_slave_data):
|
|
"""
|
|
Build and TX a RTM_NEWLINK message to add the desired interface
|
|
"""
|
|
self.logger.info("%s: netlink: ip link set dev %s: bridge port attributes" % (ifname, ifname))
|
|
self.logger.debug("attributes: %s" % ifla_info_slave_data)
|
|
|
|
try:
|
|
debug = RTM_NEWLINK in self.debug
|
|
|
|
link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
|
|
link.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
link.body = struct.pack("Bxxxiii", socket.AF_UNSPEC, 0, 0, 0)
|
|
|
|
if ifname:
|
|
link.add_attribute(Link.IFLA_IFNAME, ifname)
|
|
|
|
linkinfo = dict()
|
|
|
|
if kind:
|
|
linkinfo[Link.IFLA_INFO_KIND] = kind
|
|
linkinfo[Link.IFLA_INFO_DATA] = ifla_info_data
|
|
|
|
linkinfo[Link.IFLA_INFO_SLAVE_KIND] = "bridge"
|
|
linkinfo[Link.IFLA_INFO_SLAVE_DATA] = ifla_info_slave_data
|
|
|
|
link.add_attribute(Link.IFLA_LINKINFO, linkinfo)
|
|
link.build_message(next(self.sequence), self.pid)
|
|
|
|
# the brport already exists and is cached - after this operation we most
|
|
# likely don't need to do anything about the brport so we don't need to
|
|
# wait for the new notification to be cached.
|
|
return self.tx_nlpacket_get_response_with_error(link)
|
|
except Exception as e:
|
|
raise Exception("netlink: %s: cannot set %s (bridge slave) with options: %s" % (kind, ifname, str(e)))
|
|
|
|
def link_set_brport_with_info_slave_data_dry_run(self, ifname, kind, ifla_info_data, ifla_info_slave_data):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s: bridge port attributes" % ifname)
|
|
self.logger.debug("attributes: %s" % ifla_info_slave_data)
|
|
|
|
############################################################################
|
|
# ADDRESS
|
|
############################################################################
|
|
|
|
def addr_add_dry_run(self, ifname, addr, broadcast=None, peer=None, scope=None, preferred_lifetime=None, metric=None):
|
|
log_msg = ["netlink: ip addr add %s dev %s" % (addr, ifname)]
|
|
|
|
if scope:
|
|
log_msg.append("scope %s" % scope)
|
|
|
|
if broadcast:
|
|
log_msg.append("broadcast %s" % broadcast)
|
|
|
|
if preferred_lifetime:
|
|
log_msg.append("preferred_lft %s" % preferred_lifetime)
|
|
|
|
if peer:
|
|
log_msg.append("peer %s" % peer)
|
|
|
|
if metric:
|
|
log_msg.append("metric %s" % metric)
|
|
|
|
self.log_info_ifname_dry_run(ifname, " ".join(log_msg))
|
|
|
|
def addr_add(self, ifname, addr, broadcast=None, peer=None, scope=None, preferred_lifetime=None, metric=None, nodad=False):
|
|
log_msg = ["%s: netlink: ip addr add %s dev %s" % (ifname, addr, ifname)]
|
|
log_msg_displayed = False
|
|
try:
|
|
# We might need to check if metric/peer and other attribute are also
|
|
# correctly cached.
|
|
# We might also need to add a "force" attribute to skip the cache check
|
|
if self.cache.addr_is_cached(ifname, addr):
|
|
return
|
|
|
|
if scope:
|
|
log_msg.append("scope %s" % scope)
|
|
scope_value = RT_SCOPES.get(scope, 0)
|
|
else:
|
|
scope_value = 0
|
|
|
|
debug = RTM_NEWADDR in self.debug
|
|
|
|
packet = Address(RTM_NEWADDR, debug, use_color=self.use_color)
|
|
packet.flags = NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK
|
|
packet.family = self.IPNetwork_version_to_family.get(addr.version)
|
|
|
|
packet.add_attribute(Address.IFA_ADDRESS, addr)
|
|
packet.add_attribute(Address.IFA_LOCAL, addr)
|
|
|
|
if nodad:
|
|
log_msg.append("nodad")
|
|
packet.add_attribute(Address.IFA_FLAGS, Address.IFA_F_NODAD)
|
|
|
|
if broadcast:
|
|
log_msg.append("broadcast %s" % broadcast)
|
|
packet.add_attribute(Address.IFA_BROADCAST, ipnetwork.IPAddress(broadcast))
|
|
|
|
if preferred_lifetime:
|
|
# struct ifa_cacheinfo {
|
|
# __u32 ifa_prefered;
|
|
# __u32 ifa_valid;
|
|
# __u32 cstamp; /* created timestamp, hundredths of seconds */
|
|
# __u32 tstamp; /* updated timestamp, hundredths of seconds */
|
|
# };
|
|
log_msg.append("preferred_lft %s" % preferred_lifetime)
|
|
|
|
if preferred_lifetime.lower() == "forever":
|
|
preferred_lifetime = INFINITY_LIFE_TIME
|
|
|
|
packet.add_attribute(Address.IFA_CACHEINFO, (int(preferred_lifetime), INFINITY_LIFE_TIME, 0, 0))
|
|
|
|
if metric:
|
|
log_msg.append("metric %s" % metric)
|
|
packet.add_attribute(Address.IFA_RT_PRIORITY, int(metric))
|
|
|
|
if peer:
|
|
log_msg.append("peer %s" % peer)
|
|
|
|
# peer is already in nlmanager.ipnetwork.IPNetwork format
|
|
packet.add_attribute(Address.IFA_ADDRESS, peer)
|
|
packet_prefixlen = peer.prefixlen
|
|
else:
|
|
packet_prefixlen = addr.prefixlen
|
|
|
|
self.logger.info(" ".join(log_msg))
|
|
log_msg_displayed = True
|
|
|
|
packet.body = struct.pack("=4Bi", packet.family, packet_prefixlen, 0, scope_value, self.cache.get_ifindex(ifname))
|
|
packet.build_message(next(self.sequence), self.pid)
|
|
return self.tx_nlpacket_get_response_with_error(packet)
|
|
except Exception as e:
|
|
if not log_msg_displayed:
|
|
# just in case we get an exception before we reach the log.info
|
|
# we should display it before we raise the exception
|
|
log.info(" ".join(log_msg))
|
|
raise NetlinkError(e, "cannot add address %s dev %s" % (addr, ifname), ifname=ifname)
|
|
|
|
###
|
|
|
|
def addr_del_dry_run(self, ifname, addr):
|
|
self.log_info_ifname_dry_run(ifname, "netlink: ip addr del %s dev %s" % (addr, ifname))
|
|
|
|
def addr_del(self, ifname, addr):
|
|
if not self.cache.addr_is_cached(ifname, addr):
|
|
return
|
|
self.logger.info("%s: netlink: ip addr del %s dev %s" % (ifname, addr, ifname))
|
|
try:
|
|
debug = RTM_DELADDR in self.debug
|
|
|
|
packet = Address(RTM_DELADDR, debug, use_color=self.use_color)
|
|
packet.flags = NLM_F_REQUEST | NLM_F_ACK
|
|
packet.family = self.IPNetwork_version_to_family.get(addr.version)
|
|
packet.body = struct.pack("=4Bi", packet.family, addr.prefixlen, 0, 0, self.cache.get_ifindex(ifname))
|
|
|
|
packet.add_attribute(Address.IFA_LOCAL, addr)
|
|
|
|
packet.build_message(next(self.sequence), self.pid)
|
|
result = self.tx_nlpacket_get_response_with_error(packet)
|
|
|
|
# RTM_DELADDR successful, we need to update our cache
|
|
# to make sure we don't have any stale ip addr cached
|
|
self.cache.force_remove_addr(ifname, addr)
|
|
|
|
return result
|
|
except Exception as e:
|
|
raise NetlinkError(e, "cannot delete address %s dev %s" % (addr, ifname), ifname=ifname)
|
|
|
|
def addr_flush(self, ifname):
|
|
"""
|
|
From iproute2/ip/ipaddress.c
|
|
/*
|
|
* Note that the kernel may delete multiple addresses for one
|
|
* delete request (e.g. if ipv4 address promotion is disabled).
|
|
* Since a flush operation is really a series of delete requests
|
|
* its possible that we may request an address delete that has
|
|
* already been done by the kernel. Therefore, ignore EADDRNOTAVAIL
|
|
* errors returned from a flush request
|
|
*/
|
|
"""
|
|
for addr in self.cache.get_ip_addresses(ifname):
|
|
try:
|
|
self.addr_del(ifname, addr)
|
|
except Exception:
|
|
pass
|
|
|
|
########################
|
|
# TEMPORARY DEBUG CODE #
|
|
########################
|
|
|
|
def DEBUG_ON(self):
|
|
self.debug_link(True)
|
|
self.debug_address(True)
|
|
nllistener.log.setLevel(DEBUG)
|
|
nlpacket.log.setLevel(DEBUG)
|
|
nlmanager.log.setLevel(DEBUG)
|
|
|
|
def DEBUG_OFF(self):
|
|
self.debug_address(False)
|
|
self.debug_link(False)
|
|
nllistener.log.setLevel(WARNING)
|
|
nlpacket.log.setLevel(WARNING)
|
|
nlmanager.log.setLevel(WARNING)
|