summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Quartulli <antonio@mandelbit.com>2026-08-30 02:08:36 +0200
committerAntonio Quartulli <antonio@mandelbit.com>2026-09-13 21:06:12 +0200
commitb7e0a79b7c028cf604fb3becef3f74ae0f1f06cc (patch)
treedfc8627070ea7768585e2963a5f12cc31b1c0fe7
parentde2390d1aeb28baddc1d8caf4c85e1473910f09d (diff)
downloadvyos-1x-b7e0a79b7c028cf604fb3becef3f74ae0f1f06cc.tar.gz
vyos-1x-b7e0a79b7c028cf604fb3becef3f74ae0f1f06cc.zip
openvpn: T8264: reject configurations DCO cannot serve
The Kernel module is tun only, has no compression and no static key data path, and implements AES-GCM only. Once 2.7 makes DCO effective these combinations no longer degrade silently, they carry no traffic. Raw options are matched on their value wherever OpenVPN keeps the offload for one of them - notably "compress migrate", which it suggests for exactly that purpose.
-rwxr-xr-xsrc/conf_mode/interfaces_openvpn.py64
1 files changed, 64 insertions, 0 deletions
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index a2367cfcb..1f85d98f1 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -76,6 +76,26 @@ group = 'openvpn'
cfg_dir = '/run/openvpn'
cfg_file = '/run/openvpn/{ifname}.conf'
+# Ciphers implemented by the in-tree "ovpn" Kernel module. Any other cipher
+# must be handled in userspace and thus rules out DCO. The module also does
+# ChaCha20-Poly1305, which the CLI does not offer.
+dco_ciphers = ['aes128gcm', 'aes192gcm', 'aes256gcm']
+# Raw options that make OpenVPN fall back to the userspace data path, taken
+# from dco_check_option() and dco_check_option_ce()
+dco_incompatible_options = [
+ 'comp-lzo',
+ 'disable-dco',
+ 'fragment',
+ 'http-proxy',
+ 'management-query-proxy',
+ 'socks-proxy',
+]
+# these rule out the offload for every value but one - notably "compress
+# migrate", which is what OpenVPN suggests to keep it
+dco_conditional_options = {
+ 'allow-compression': 'no',
+ 'compress': 'migrate',
+}
otp_path = '/config/auth/openvpn'
otp_file = '/config/auth/openvpn/{ifname}-otp-secrets'
secret_chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
@@ -209,6 +229,49 @@ def verify_data_ciphers_fallback(openvpn):
if dict_search('encryption.data_ciphers_fallback', openvpn):
raise ConfigError('Cipher fallback is valid only in site-to-site mode')
+def verify_dco(openvpn):
+ if dict_search('offload.dco', openvpn) is None:
+ return
+
+ if openvpn['device_type'] != 'tun':
+ raise ConfigError('DCO requires "device-type tun"')
+
+ if openvpn['mode'] == 'server':
+ topology = dict_search('server.topology', openvpn)
+ if topology != 'subnet':
+ raise ConfigError(
+ f'DCO requires "server topology subnet", got "{topology}"'
+ )
+
+ if 'shared_secret_key' in openvpn:
+ raise ConfigError('DCO is incompatible with "shared-secret-key"')
+
+ if 'use_lzo_compression' in openvpn:
+ raise ConfigError('DCO is incompatible with "use-lzo-compression"')
+
+ ciphers = dict_search('encryption.data_ciphers', openvpn) or []
+ fallback = dict_search('encryption.data_ciphers_fallback', openvpn)
+ if fallback:
+ ciphers = ciphers + [fallback]
+
+ for cipher in ciphers:
+ if cipher not in dco_ciphers:
+ raise ConfigError(f'DCO does not support cipher "{cipher}"')
+
+ # A raw option OpenVPN refuses to offload leaves the daemon on the
+ # userspace data path, where it can not use the interface it was given
+ for option in dict_search('openvpn_option', openvpn) or []:
+ tmp = option.split()
+ if not tmp:
+ continue
+ keyword = tmp[0].lstrip('-')
+ if keyword in dco_incompatible_options:
+ raise ConfigError(f'DCO is incompatible with "openvpn-option {keyword}"')
+ if keyword in dco_conditional_options:
+ keep = dco_conditional_options[keyword]
+ if tmp[1:] != [keep]:
+ raise ConfigError(f'DCO requires "openvpn-option {keyword} {keep}"')
+
def verify_pki(openvpn):
pki = openvpn['pki']
interface = openvpn['ifname']
@@ -662,6 +725,7 @@ def verify(openvpn):
verify_mirror_redirect(openvpn)
verify_data_ciphers_fallback(openvpn)
+ verify_dco(openvpn)
return None