Page MenuHomeVyOS Platform

Setting OpenVPN site-to-site vtunX Interface Description Fails and Disrupts Service
Open, HighPublicBUG

Description

Problem Description

Attempting to configure persistent-tunnel OpenVPN vtunX interface description fails in VyOS 1.4.0+ (encountered in 1.4.3)

Problem Cause

[ Verification Needed ] This appears to be a race condition with attempting to restart the OpenVPN service which briefly tears down and recreates the associated vtunX interfaces.

Potential Fix

In python/vyos/ifconfig/interface.py:

def set_alias(self, name: str):
-    tmp = self.get_interface('alias')
-    if tmp != name:
-        self.set_interface('alias', name)
+    try:
+        tmp = self.get_interface('alias')
+    except OSError:
+        # transient: device may be recreating; wait briefly
+        for _ in range(10):
+            if self.exists(self.ifname):
+                break
+            time.sleep(0.1)
+        tmp = ''  # fall through and attempt to set
+    if tmp != name and self.exists(self.ifname):
+        self.set_interface('alias', name)
Recommended Secondary Change: Make setting interface description non-disruptive:

In src/conf_mode/interfaces_openvpn.py:

- o.update(openvpn)
+ # If only description changed, avoid bouncing the daemon.
+ if only_description_changed:
+     if Interface.exists(ifname):
+         o.set_alias(config.get('description', ''))
+     return
+ o.update(openvpn)
Steps to Recreate

Configure OpenVPN site-to-site interfaces and try to change the description;

Terminal Session when Encountered:
rps@vfw-o-site-vpn# set interfaces openvpn vtun4 description 'vfw-x-starlink'
[edit]
rps@vfw-o-site-vpn# commit
[ interfaces openvpn vtun4 ]
VyOS had an issue completing a command.

We are sorry that you encountered a problem while using VyOS.
There are a few things you can do to help us (and yourself):
- Contact us using the online help desk if you have a subscription:
  https://support.vyos.io/
- Make sure you are running the latest version of VyOS available at:
  https://vyos.net/get/
- Consult the community forum to see how to handle this issue:
  https://forum.vyos.io
- Join us on Slack where our users exchange help and advice:
  https://vyos.slack.com

When reporting problems, please include as much information as possible:
- do not obfuscate any data (feel free to contact us privately if your 
  business policy requires it)
- and include all the information presented below

Report time:      2025-08-25 07:22:05
Image version:    VyOS 1.4.3
Release train:    sagitta

Built by:         autobuild@vyos.net
Built on:         Mon 07 Jul 2025 14:34 UTC
Build UUID:       b0ae922b-2396-42b7-bb82-a1ebf406306d
Build commit ID:  f327543504e3da-dirty

Architecture:     x86_64
Boot via:         installed image
System type:      bare metal

Hardware vendor:  Supermicro
Hardware model:   Super Server
Hardware S/N:     0123456789
Hardware UUID:    00000000-0000-0000-0000-3cecef0cb930

Traceback (most recent call last):
  File "/usr/libexec/vyos/conf_mode/interfaces_openvpn.py", line 757, in <module>
    apply(c)
  File "/usr/libexec/vyos/conf_mode/interfaces_openvpn.py", line 747, in apply
    o.update(openvpn)
  File "/usr/lib/python3/dist-packages/vyos/ifconfig/interface.py", line 1577, in update
    self.set_alias(config.get('description', ''))
  File "/usr/lib/python3/dist-packages/vyos/ifconfig/interface.py", line 977, in set_alias
    tmp = self.get_interface('alias')
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/vyos/ifconfig/control.py", line 176, in get_interface
    return self._get_command(self.config, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/vyos/ifconfig/control.py", line 59, in _get_command
    return self._command_get[name].get('format', lambda _: _)(self._cmd(cmd))
                                                              ^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/vyos/ifconfig/control.py", line 52, in _cmd
    return cmd(command, self.debug)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/vyos/utils/process.py", line 155, in cmd
    raise OSError(code, feedback)
PermissionError: [Errno 1] failed to run command: ip -json -detail link list dev vtun4
returned: 
exit code: 1

noteworthy:
cmd 'ip -json -detail link list dev vtun4'
returned (out):

returned (err):
Device "vtun4" does not exist.

[[interfaces openvpn vtun4]] failed
Commit failed
[edit]
Additional Detail:

Manually looking up interfaces as root does return data despite the commit script reporting no data was returned-

root@vfw-o-site-vpn:~# ip -json -detail link list dev vtun1
[{"ifindex":11,"ifname":"vtun1","flags":["POINTOPOINT","MULTICAST","NOARP","UP","LOWER_UP"],"mtu":1500,"qdisc":"noqueue","operstate":"UNKNOWN","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"none","promiscuity":0,"allmulti":0,"min_mtu":68,"max_mtu":65463,"linkinfo":{"info_kind":"ovpn-dco"},"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535,"tso_max_size":65536,"tso_max_segs":65535,"gro_max_size":65536,"gso_ipv4_max_size":65536,"gro_ipv4_max_size":65536}]
root@vfw-o-site-vpn:~# ip -json -detail link list dev vtun2
[{"ifindex":9,"ifname":"vtun2","flags":["POINTOPOINT","MULTICAST","NOARP","UP","LOWER_UP"],"mtu":1500,"qdisc":"noqueue","operstate":"UNKNOWN","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"none","promiscuity":0,"allmulti":0,"min_mtu":68,"max_mtu":65463,"linkinfo":{"info_kind":"ovpn-dco"},"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535,"tso_max_size":65536,"tso_max_segs":65535,"gro_max_size":65536,"gso_ipv4_max_size":65536,"gro_ipv4_max_size":65536}]
root@vfw-o-site-vpn:~# ip -json -detail link list dev vtun3
[{"ifindex":8,"ifname":"vtun3","flags":["POINTOPOINT","MULTICAST","NOARP","UP","LOWER_UP"],"mtu":1500,"qdisc":"noqueue","operstate":"UNKNOWN","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"none","promiscuity":0,"allmulti":0,"min_mtu":68,"max_mtu":65463,"linkinfo":{"info_kind":"ovpn-dco"},"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535,"tso_max_size":65536,"tso_max_segs":65535,"gro_max_size":65536,"gso_ipv4_max_size":65536,"gro_ipv4_max_size":65536}]
root@vfw-o-site-vpn:~# ip -json -detail link list dev vtun4
[{"ifindex":10,"ifname":"vtun4","flags":["POINTOPOINT","MULTICAST","NOARP","UP","LOWER_UP"],"mtu":1300,"qdisc":"noqueue","operstate":"UNKNOWN","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"none","promiscuity":0,"allmulti":0,"min_mtu":68,"max_mtu":65463,"linkinfo":{"info_kind":"ovpn-dco"},"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535,"tso_max_size":65536,"tso_max_segs":65535,"gro_max_size":65536,"gso_ipv4_max_size":65536,"gro_ipv4_max_size":65536}]

Details

Version
1.4.3
Is it a breaking change?
Unspecified (possibly destroys the router)
Issue type
Bug (incorrect behavior)

Event Timeline

I have tested it in the lab with 1.4.3, but I cannot reproduce it. But during the implementation of OpenVPN for our customer, I met this issue. It happened when the config was too large (around 9K command lines) and the system was overloaded (old servers or GNS3).

Reproduced on rolling with openvpn 2.7.8, on a lightly loaded lab router in client mode: set interfaces openvpn vtun31 description <x> + commit on a running client gives

PermissionError: [Errno 1] failed to run command: ip --detail --json link show dev vtun31

The commit returns 0, the session stays dirty and the description is not applied. With openvpn 2.6.14 (1.5.1, current circinus) the same steps commit cleanly, so openvpn 2.7 turns the race reported here into a deterministic failure.

Cause: apply() runs reload-or-restart for the description change and then immediately VTunIf.update(), whose set_alias() reads the interface. Right after the restart the tun device does not exist yet; the same command succeeds a few seconds later. Probably the client recreates the device only after the server's PUSH_REPLY, but that part is not verified.

Suggested fix: do not restart the daemon for a description-only change, and make update() wait for the device (or skip it until the daemon has recreated it) after a restart.

Viacheslav raised the priority of this task from Normal to High.Thu, Oct 29, 5:31 PM