mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Current design destroys existing tunnel when a config change is detected. This behaviour causes traffic loss. Signed-off-by: Julien Fortin <jfortin@nvidia.com>
309 lines
11 KiB
Python
309 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Maximilian Wilhelm <max@rfc2324.org>
|
|
# -- Mon 10 Oct 2016 10:53:13 PM CEST
|
|
#
|
|
try:
|
|
from ifupdown2.lib.addon import Addon
|
|
from ifupdown2.nlmanager.nlmanager import Link
|
|
|
|
from ifupdown2.ifupdown.iface import *
|
|
|
|
from ifupdown2.ifupdownaddons.modulebase import moduleBase
|
|
|
|
import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
|
|
import ifupdown2.nlmanager.ipnetwork as ipnetwork
|
|
except (ImportError, ModuleNotFoundError):
|
|
from lib.addon import Addon
|
|
from nlmanager.nlmanager import Link
|
|
|
|
from ifupdown.iface import *
|
|
|
|
from ifupdownaddons.modulebase import moduleBase
|
|
|
|
import ifupdown.ifupdownflags as ifupdownflags
|
|
import nlmanager.ipnetwork as ipnetwork
|
|
|
|
|
|
#
|
|
# TODO: Add checks for ipip tunnels.
|
|
#
|
|
class tunnel(Addon, moduleBase):
|
|
"""
|
|
ifupdown2 addon module to configure tunnels
|
|
"""
|
|
_modinfo = {
|
|
'mhelp': 'create/configure GRE/IPIP/SIT and GRETAP tunnel interfaces',
|
|
'attrs': {
|
|
'tunnel-mode': {
|
|
'help': 'type of tunnel as in \'ip link\' command.',
|
|
'validvals': ['gre', 'gretap', 'ipip', 'sit', 'vti', 'ip6gre', 'ipip6', 'ip6ip6', 'vti6'],
|
|
'required': True,
|
|
'example': ['tunnel-mode gre'],
|
|
"aliases": ["mode"]
|
|
},
|
|
'tunnel-local': {
|
|
'help': 'IP of local tunnel endpoint',
|
|
'validvals': ['<ipv4>', '<ipv6>'],
|
|
'required': True,
|
|
'example': ['tunnel-local 192.2.0.42'],
|
|
"aliases": ["local"]
|
|
},
|
|
'tunnel-endpoint': {
|
|
'help': 'IP of remote tunnel endpoint',
|
|
'validvals': ['<ipv4>', '<ipv6>'],
|
|
'required': True,
|
|
'example': ['tunnel-endpoint 192.2.0.23'],
|
|
"aliases": ["endpoint"]
|
|
},
|
|
'tunnel-ttl': {
|
|
'help': 'TTL for tunnel packets (range 0..255), 0=inherit',
|
|
"validrange": ["0", "255"],
|
|
'validvals': ['<number>', 'inherit'],
|
|
'required': False,
|
|
'example': ['tunnel-ttl 64'],
|
|
"aliases": ["ttl"]
|
|
},
|
|
'tunnel-tos': {
|
|
'help': 'TOS for tunnel packets (range 0..255), 1=inherit',
|
|
"validrange": ["0", "255"],
|
|
'validvals': ['<number>', 'inherit'],
|
|
'required': False,
|
|
'example': ['tunnel-tos inherit'],
|
|
"aliases": ["tos"]
|
|
},
|
|
'tunnel-dev': {
|
|
'help': 'Physical underlay device to use for tunnel packets',
|
|
'validvals': ['<interface>'],
|
|
'required': False,
|
|
'example': ['tunnel-dev eth1'],
|
|
"aliases": ["tunnel-physdev"]
|
|
},
|
|
}
|
|
}
|
|
|
|
def __init__(self, *args, **kargs):
|
|
moduleBase.__init__(self, *args, **kargs)
|
|
Addon.__init__(self)
|
|
|
|
@staticmethod
|
|
def _is_my_interface(ifaceobj):
|
|
return ifaceobj.get_attr_value_first("tunnel-mode")
|
|
|
|
@staticmethod
|
|
def _has_config_changed(attrs_present, attrs_configured):
|
|
for key, value in attrs_configured.items():
|
|
if attrs_present.get(key) != value:
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def _get_tunnel_ttl(ttl_config):
|
|
if ttl_config and ttl_config == "inherit":
|
|
return "0"
|
|
return ttl_config
|
|
|
|
@staticmethod
|
|
def _get_tunnel_tos(tos_config):
|
|
if tos_config and tos_config == "inherit":
|
|
return "1"
|
|
return tos_config
|
|
|
|
def __get_info_data_gre_tunnel(self, info_data):
|
|
tunnel_link_ifindex = info_data.get(Link.IFLA_GRE_LINK)
|
|
|
|
return {
|
|
"tunnel-endpoint": info_data.get(Link.IFLA_GRE_REMOTE),
|
|
"tunnel-local": info_data.get(Link.IFLA_GRE_LOCAL),
|
|
"tunnel-ttl": str(info_data.get(Link.IFLA_GRE_TTL)),
|
|
"tunnel-tos": str(info_data.get(Link.IFLA_GRE_TOS)),
|
|
"tunnel-dev": self.cache.get_ifname(tunnel_link_ifindex) if tunnel_link_ifindex else ""
|
|
}
|
|
|
|
def __get_info_data_iptun_tunnel(self, info_data):
|
|
tunnel_link_ifindex = info_data.get(Link.IFLA_IPTUN_LINK)
|
|
|
|
return {
|
|
"tunnel-endpoint": info_data.get(Link.IFLA_IPTUN_REMOTE),
|
|
"tunnel-local": info_data.get(Link.IFLA_IPTUN_LOCAL),
|
|
"tunnel-ttl": str(info_data.get(Link.IFLA_IPTUN_TTL)),
|
|
"tunnel-tos": str(info_data.get(Link.IFLA_IPTUN_TOS)),
|
|
"tunnel-dev": self.cache.get_ifname(tunnel_link_ifindex) if tunnel_link_ifindex else ""
|
|
}
|
|
|
|
def __get_info_data_vti_tunnel(self, info_data):
|
|
tunnel_link_ifindex = info_data.get(Link.IFLA_VTI_LINK)
|
|
|
|
return {
|
|
"tunnel-endpoint": info_data.get(Link.IFLA_VTI_REMOTE),
|
|
"tunnel-local": info_data.get(Link.IFLA_VTI_LOCAL),
|
|
"tunnel-dev": self.cache.get_ifname(tunnel_link_ifindex) if tunnel_link_ifindex else ""
|
|
}
|
|
|
|
def get_linkinfo_attrs(self, ifname, link_kind):
|
|
return {
|
|
"gre": self.__get_info_data_gre_tunnel,
|
|
"gretap": self.__get_info_data_gre_tunnel,
|
|
"ip6gre": self.__get_info_data_gre_tunnel,
|
|
"ip6gretap": self.__get_info_data_gre_tunnel,
|
|
"ip6erspan": self.__get_info_data_gre_tunnel,
|
|
"ipip": self.__get_info_data_iptun_tunnel,
|
|
"sit": self.__get_info_data_iptun_tunnel,
|
|
"ip6tnl": self.__get_info_data_iptun_tunnel,
|
|
"vti": self.__get_info_data_vti_tunnel,
|
|
"vti6": self.__get_info_data_vti_tunnel,
|
|
}.get(link_kind, lambda x: {})(self.cache.get_link_info_data(ifname))
|
|
|
|
def _up(self, ifaceobj):
|
|
ifname = ifaceobj.name
|
|
attr_map = {
|
|
# attr_name -> ip route param name
|
|
'tunnel-local': 'local',
|
|
'tunnel-endpoint': 'remote',
|
|
'tunnel-ttl': 'ttl',
|
|
'tunnel-tos': 'tos',
|
|
'tunnel-dev': 'dev',
|
|
}
|
|
|
|
mode = ifaceobj.get_attr_value_first('tunnel-mode')
|
|
attrs = {}
|
|
attrs_mapped = {}
|
|
|
|
# Only include attributes which have been set and map ifupdown2 names
|
|
# to attribute names expected by iproute
|
|
for attr, iproute_attr in list(attr_map.items()):
|
|
attr_val = ifaceobj.get_attr_value_first(attr)
|
|
if attr_val is not None:
|
|
attrs_mapped[iproute_attr] = attr_val
|
|
attrs[attr] = attr_val
|
|
|
|
# convert ip route 'tos' param into hex format (00..ff)
|
|
tos = attrs_mapped.get('tos')
|
|
if tos and tos != 'inherit':
|
|
attrs_mapped['tos'] = "{:x}".format(int(tos))
|
|
|
|
link_exists = self.cache.link_exists(ifname)
|
|
|
|
# Create the tunnel if it doesn't exist yet...
|
|
if not link_exists:
|
|
self.iproute2.tunnel_create(ifname, mode, attrs_mapped)
|
|
return
|
|
|
|
# If it's present, check if there were changes
|
|
current_mode = self.cache.get_link_kind(ifname)
|
|
current_attrs = self.get_linkinfo_attrs(ifname, current_mode)
|
|
|
|
self.convert_user_config_to_ipnetwork(attrs, "tunnel-local")
|
|
self.convert_user_config_to_ipnetwork(attrs, "tunnel-endpoint")
|
|
|
|
try:
|
|
if current_attrs and current_mode != mode or self._has_config_changed(current_attrs, attrs):
|
|
|
|
if link_exists and current_mode != mode:
|
|
# Mode and some other changes are not possible without recreating the interface,
|
|
# so just recreate it IFF there have been changes.
|
|
self.netlink.link_del(ifaceobj.name)
|
|
link_exists = False
|
|
|
|
self.iproute2.tunnel_create(ifaceobj.name, mode, attrs_mapped, link_exists=link_exists)
|
|
except Exception as e:
|
|
self.log_error(str(e), ifaceobj)
|
|
|
|
def _down(self, ifaceobj):
|
|
if not ifupdownflags.flags.PERFMODE and not self.cache.link_exists(ifaceobj.name):
|
|
return
|
|
try:
|
|
self.netlink.link_del(ifaceobj.name)
|
|
except Exception as e:
|
|
self.log_warn(str(e))
|
|
|
|
def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None, old_ifaceobjs=False):
|
|
if not self._is_my_interface(ifaceobj):
|
|
return None
|
|
|
|
device = ifaceobj.get_attr_value_first('tunnel-dev')
|
|
if device:
|
|
return [device]
|
|
|
|
return None
|
|
|
|
@staticmethod
|
|
def _query_check_n_update(ifaceobjcurr, attrname, attrval, running_attrval):
|
|
if running_attrval and attrval == running_attrval:
|
|
ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
|
|
else:
|
|
ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
|
|
|
|
def convert_user_config_to_ipnetwork(self, user_config, attr_name):
|
|
"""
|
|
Ideally this convertion should be done by ifupdown2 at a lower level
|
|
(after parsing /e/n/i) and should be done directly on each ifaceobj.
|
|
"""
|
|
try:
|
|
user_config[attr_name] = ipnetwork.IPNetwork(user_config[attr_name])
|
|
except Exception:
|
|
pass
|
|
|
|
def _query_check(self, ifaceobj, ifaceobjcurr):
|
|
ifname = ifaceobj.name
|
|
|
|
if not self.cache.link_exists(ifname):
|
|
return
|
|
|
|
link_kind = self.cache.get_link_kind(ifname)
|
|
tunattrs = self.get_linkinfo_attrs(ifaceobj.name, link_kind)
|
|
|
|
if not tunattrs:
|
|
ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1)
|
|
return
|
|
|
|
tunattrs["tunnel-mode"] = link_kind
|
|
|
|
user_config_mode = ifaceobj.get_attr_value_first("tunnel-mode")
|
|
if user_config_mode in ('ipip6', 'ip6ip6'):
|
|
ifaceobj.replace_config("tunnel-mode", "ip6tnl")
|
|
|
|
for attr, netlink_func in (
|
|
("tunnel-mode", None),
|
|
("tunnel-local", ipnetwork.IPNetwork),
|
|
("tunnel-endpoint", ipnetwork.IPNetwork),
|
|
("tunnel-ttl", self._get_tunnel_ttl),
|
|
("tunnel-tos", self._get_tunnel_tos),
|
|
("tunnel-dev", None),
|
|
):
|
|
attr_value = ifaceobj.get_attr_value_first(attr)
|
|
|
|
if not attr_value:
|
|
continue
|
|
|
|
if callable(netlink_func):
|
|
attr_value = netlink_func(attr_value)
|
|
|
|
# Validate all interface attributes set in the config.
|
|
# Remote any leading 'tunnel-' prefix in front of the attr name
|
|
# when accessing tunattrs parsed from 'ip -d link'.
|
|
self._query_check_n_update(ifaceobjcurr, attr, attr_value, tunattrs.get(attr))
|
|
|
|
# Operations supported by this addon (yet).
|
|
_run_ops = {
|
|
'pre-up': _up,
|
|
'post-down': _down,
|
|
'query-checkcurr': _query_check
|
|
}
|
|
|
|
def get_ops(self):
|
|
return list(self._run_ops.keys())
|
|
|
|
def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
|
|
op_handler = self._run_ops.get(operation)
|
|
if not op_handler:
|
|
return
|
|
|
|
if operation != 'query-running' and not self._is_my_interface(ifaceobj):
|
|
return
|
|
|
|
if operation == 'query-checkcurr':
|
|
op_handler(self, ifaceobj, query_ifaceobj)
|
|
else:
|
|
op_handler(self, ifaceobj)
|