diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-09-16 20:48:14 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-16 20:48:14 +0200 |
| commit | 7943d798dcb4c860610d3e1b2869175a9147c083 (patch) | |
| tree | b84e4178b13a9c9f0a39b96fdd46f1754e283bd1 /src | |
| parent | 5b0cce51f6b9e43ef204d6fba54f5f5c802e759f (diff) | |
| parent | 963e4cc5b36b9efd8fec9fe52a6fbacee6f4a2e5 (diff) | |
| download | vyos-1x-7943d798dcb4c860610d3e1b2869175a9147c083.tar.gz vyos-1x-7943d798dcb4c860610d3e1b2869175a9147c083.zip | |
Merge pull request #5441 from ordex/T8264-openvpn-dco-lifecycle
T8264: openvpn dco lifecycle
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/interfaces_openvpn.py | 61 |
1 files changed, 47 insertions, 14 deletions
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py index ff5dcf07b..2b235c057 100755 --- a/src/conf_mode/interfaces_openvpn.py +++ b/src/conf_mode/interfaces_openvpn.py @@ -41,6 +41,9 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import VTunIf +from vyos.netlink.ovpn import get_ovpn_mode +from vyos.netlink.ovpn import OVPN_MODE_MP +from vyos.netlink.ovpn import OVPN_MODE_P2P from vyos.pki import load_dh_parameters from vyos.pki import load_private_key from vyos.pki import sort_ca_chain @@ -65,6 +68,7 @@ from vyos.utils.permission import chown from vyos.utils.process import cmdl from vyos.utils.network import is_addr_assigned from vyos.utils.network import interface_exists +from vyos.utils.network import get_interface_config from vyos import ConfigError from vyos import airbag @@ -144,6 +148,18 @@ def get_config(config=None): ifname, openvpn = get_interface_dict(conf, base, with_pki=True) openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) + # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all + # OpenVPN interfaces. Check if DCO is used by any other interface instance. + tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + for interface, interface_config in tmp.items(): + # If one interface has DCO configured, enable it. No need to further check + # all other OpenVPN interfaces. We must use a dedicated key to indicate + # the Kernel module must be loaded or not. The per interface "offload.dco" + # key is required per OpenVPN interface instance. + if dict_search('offload.dco', interface_config) != None: + openvpn['module_load_dco'] = {} + break + if 'deleted' in openvpn: return openvpn @@ -166,8 +182,12 @@ def get_config(config=None): if is_node_changed(conf, base + [ifname, 'openvpn-option']): openvpn.update({'restart_required': {}}) - if is_node_changed(conf, base + [ifname, 'offload', 'dco']): - openvpn.update({'restart_required': {}}) + # the offload, the operating mode and the device type all decide what kind + # of interface the data path needs, which can only change on a restart + for node in [['offload', 'dco'], ['mode'], ['device-type']]: + if is_node_changed(conf, base + [ifname] + node): + openvpn.update({'restart_required': {}}) + break # Detect changes that are limited to per-client CCD entries (T6478). # OpenVPN reads client-config-dir files at connect time, so adding or @@ -187,18 +207,6 @@ def get_config(config=None): if dict_search('server.mfa.totp', tmp) == None: del openvpn['server']['mfa'] - # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all - # OpenVPN interfaces. Check if DCO is used by any other interface instance. - tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - for interface, interface_config in tmp.items(): - # If one interface has DCO configured, enable it. No need to further check - # all other OpenVPN interfaces. We must use a dedicated key to indicate - # the Kernel module must be loaded or not. The per interface "offload.dco" - # key is required per OpenVPN interface instance. - if dict_search('offload.dco', interface_config) != None: - openvpn['module_load_dco'] = {} - break - # Calculate the protocol modifier. This is concatenated to the protocol string to direct # OpenVPN to use a specific IP protocol version. If unspecified, the kernel decides which # type of socket to open. In server mode, an additional "ipv6-dual-stack" option forces @@ -978,6 +986,31 @@ def apply(openvpn): if not is_addr_assigned(openvpn['local_host']): cmdl(['sysctl', '-w', 'net.ipv4.ip_nonlocal_bind=1']) + # The interface type follows the data path, and OpenVPN adopts whatever it + # finds - including an "ovpn" device in the wrong operating mode, which + # then rejects every peer. Drop a leftover that no longer matches. Only do + # so when the daemon is restarted below, or a commit that leaves it running + # would take the interface away from underneath it. + if 'restart_required' in openvpn and interface_exists(interface): + if dict_search('offload.dco', openvpn) is None: + drop = get_ovpn_mode(interface) is not None + elif openvpn['mode'] == 'server': + drop = get_ovpn_mode(interface) != OVPN_MODE_MP + else: + drop = get_ovpn_mode(interface) != OVPN_MODE_P2P + + # The Kernel pins tun against tap when the device is made and refuses + # to hand a "tap" device to a daemon asking for a "tun" one. An "ovpn" + # device carries no such type, hence the None. + if not drop: + tmp = dict_search( + 'linkinfo.info_data.type', get_interface_config(interface) + ) + drop = tmp is not None and tmp != openvpn['device_type'] + + if drop: + VTunIf(interface).remove() + # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process |
