summaryrefslogtreecommitdiff
path: root/src/migration-scripts
diff options
context:
space:
mode:
authorAntonio Quartulli <antonio@mandelbit.com>2026-09-03 01:46:38 +0200
committerAntonio Quartulli <antonio@mandelbit.com>2026-09-13 21:06:12 +0200
commit043992126c638940d26ee075de84d8d457bd446d (patch)
tree4dbc51e02deee1ef510cd180d5ea5a51d3f59a13 /src/migration-scripts
parent200bbf96f439f94c6b52a1e99c3d2b27f27cff42 (diff)
downloadvyos-1x-043992126c638940d26ee075de84d8d457bd446d.tar.gz
vyos-1x-043992126c638940d26ee075de84d8d457bd446d.zip
openvpn: T8264: reject the raw options DCO cannot serve
Raw options are appended after --config and therefore override what the CLI rendered, so they have to face the same checks. A single cipher the Kernel module does not implement makes OpenVPN fall back to the userspace data path; "ncp-ciphers" is the 2.4 name for the negotiation list, and outside server and pull mode "cipher" becomes the fallback cipher. "dev-type" other than tun, an AF_UNIX "dev-node" and, in server mode, a "topology" other than subnet drop the offload just the same. "DEFAULT" stands for the built-in list, which OpenVPN expands to AEAD ciphers alone before it weighs the offload, and the migration drops the offload from whatever stays rejected.
Diffstat (limited to 'src/migration-scripts')
-rw-r--r--src/migration-scripts/openvpn/5-to-645
1 files changed, 41 insertions, 4 deletions
diff --git a/src/migration-scripts/openvpn/5-to-6 b/src/migration-scripts/openvpn/5-to-6
index 8f1abc99d..9a6de85cd 100644
--- a/src/migration-scripts/openvpn/5-to-6
+++ b/src/migration-scripts/openvpn/5-to-6
@@ -33,11 +33,41 @@ dco_incompatible_options = [
dco_conditional_options = {
'allow-compression': 'no',
'compress': 'migrate',
+ 'dev-type': 'tun',
}
+dco_cipher_options = ['data-ciphers', 'data-ciphers-fallback', 'ncp-ciphers']
+dco_raw_ciphers = ['AES-128-GCM', 'AES-192-GCM', 'AES-256-GCM', 'CHACHA20-POLY1305']
-def _offloadable(options: list) -> bool:
+def _offloadable_ciphers(value: str) -> bool:
+ """Whether every cipher of a raw negotiation list can be offloaded."""
+ for cipher in value.split(':'):
+ # "DEFAULT" stands for the built-in list, which OpenVPN expands - case
+ # sensitively, and only as a bare token - to AEAD ciphers alone before
+ # it weighs the offload
+ if cipher == 'DEFAULT':
+ continue
+ # it strips exactly one "?", and drops such a cipher only when it does
+ # not know it at all, so an optional one still has to be offloadable
+ name = cipher[1:] if cipher.startswith('?') else cipher
+ if name.upper() not in dco_raw_ciphers:
+ return False
+ return True
+
+
+def _offloadable(options: list, mode: str) -> bool:
"""Whether every raw option leaves the data path offloadable."""
+ # site-to-site renders neither "client" nor "server", and OpenVPN then
+ # takes a raw "--cipher" as the fallback cipher
+ cipher_options = dco_cipher_options
+ if mode == 'site-to-site':
+ cipher_options = cipher_options + ['cipher']
+
+ # "topology" only reaches OpenVPN's decision in server mode
+ conditional_options = dco_conditional_options
+ if mode == 'server':
+ conditional_options = {**conditional_options, 'topology': 'subnet'}
+
for option in options:
tmp = option.split()
if not tmp:
@@ -45,9 +75,15 @@ def _offloadable(options: list) -> bool:
keyword = tmp[0].lstrip('-')
if keyword in dco_incompatible_options:
return False
- keep = dco_conditional_options.get(keyword)
+ keep = conditional_options.get(keyword)
if keep is not None and tmp[1:] != [keep]:
return False
+ # only an AF_UNIX node rules out the offload, a real one is fine
+ if keyword == 'dev-node' and tmp[1:] and tmp[1].startswith('unix:'):
+ return False
+ if keyword in cipher_options and tmp[1:]:
+ if not _offloadable_ciphers(tmp[1]):
+ return False
return True
@@ -104,8 +140,9 @@ def migrate(config: ConfigTree) -> None:
if config.exists(path + ['openvpn-option']):
options = config.return_values(path + ['openvpn-option'])
+ mode = _value(config, path + ['mode'])
topology = None
- if _value(config, path + ['mode']) == 'server':
+ if mode == 'server':
topology = _value(config, path + ['server', 'topology'])
if (
@@ -114,7 +151,7 @@ def migrate(config: ConfigTree) -> None:
or config.exists(path + ['use-lzo-compression'])
or (topology is not None and topology != 'subnet')
or any(cipher not in dco_ciphers for cipher in ciphers)
- or not _offloadable(options)
+ or not _offloadable(options, mode)
):
offload = path + ['offload']
config.delete(offload + ['dco'])