REPRODUCIBILITY: 3/3. All three clean restarts that I recorded brought the VPN back as wg1 (5.2.0.20 on 2026-10-25 and 2026-10-26, 5.2.0.22 on 2026-10-27; on 2026-10-26 I recorded only the default route, which moved from wg0 to wg1). The code path below suggests it happens on every connman-vpnd shutdown while a WireGuard VPN is connected
OS VERSION: 5.2.0.20 and 5.2.0.22, with the same connman packages in both (connman 1.40+git26.6-1.43.1.jolla, connman-plugin-vpn-wireguard 1.40+git26.6-1.43.1.jolla); kernel 6.12.38 in both
HARDWARE: Jolla Phone (2026), jp2601
UI LANGUAGE: Finnish
REGRESSION: Unknown (not tested on earlier releases)
DESCRIPTION:
When connman-vpnd stops while a WireGuard VPN is connected, it does not delete the WireGuard network interface. I triggered this with systemctl restart connman, which also restarts connman-vpn.service because that unit has Requires=connman.service. The old wg0 stays in the kernel, down but still present. When the VPN reconnects, connman-vpnd skips the taken name and creates wg1. The stale interface stays until a reboot or a manual ip link del.
A normal Disconnect/Connect deletes the interface correctly. On 5.2.0.20, my watchdog disconnected and reconnected the VPN 38 times in about 20 hours after a boot, and 23 hours after the boot there was still exactly one wg0. Only the daemon shutdown path leaks the interface.
PRECONDITIONS:
- A WireGuard VPN created in Settings > VPN by importing a .conf file (endpoint given as an IP address), with automatic connection (AutoConnect) on, and connected.
- Developer mode with root access (devel-su) to restart the service.
STEPS TO REPRODUCE:
- With the VPN connected, run as defaultuser:
/usr/sbin/ip -br link | grep '^wg' /usr/sbin/ip route show default - Restart ConnMan as root:
devel-su systemctl restart connman - Wait until the VPN has reconnected (2–6 s here).
- Run the commands from step 1 again.
devel-su systemctl restart connman-vpn should go through the same shutdown path, but I did not test it separately.
EXPECTED RESULT:
connman-vpnd deletes its WireGuard interface when it stops. After the reconnect there is one WireGuard interface (wg0 again), as after a normal Disconnect/Connect.
ACTUAL RESULT:
On 5.2.0.20 (2026-10-25), before the restart there was one tunnel interface, wg0 (ifindex 44):
default dev wg0 scope link
After the restart and reconnect:
$ ip -br link | grep '^wg'
wg0 DOWN <POINTOPOINT,NOARP,DYNAMIC>
wg1 UNKNOWN <POINTOPOINT,NOARP,DYNAMIC,UP,LOWER_UP>
$ ip route show default
default dev wg1 scope link
The stale wg0 kept ifindex 44, the index the tunnel had before the restart. During the reconnect the VPN was set up twice, first over mobile data and then over WLAN once it came up. Both times the name was wg1, so the normal disconnect path deleted wg1 in between. Only the wg0 from before the restart was left behind. It was still there 5 minutes later, and only the next reboot cleared it.
Same on 5.2.0.22: I restarted ConnMan the same way on 2026-10-27, about 11 minutes after boot. The next day:
$ ip -br link | grep '^wg'
wg0 DOWN <POINTOPOINT,NOARP,DYNAMIC>
wg1 UNKNOWN <POINTOPOINT,NOARP,DYNAMIC,UP,LOWER_UP>
$ ip route show default
default dev wg1 scope link
The stale wg0 (ifindex 40) is the tunnel from before the restart. By then the VPN had been reconnected at least 8 more times (ifindex 42 to 50 in my address log), always as wg1, and each old wg1 was deleted. The stale wg0 had been there for more than 26 hours.
For comparison, on 5.2.0.20 (2026-10-26), after 38 Disconnect/Connect cycles since the last boot:
$ ip -br link | grep '^wg'
wg0 UNKNOWN <POINTOPOINT,NOARP,DYNAMIC,UP,LOWER_UP>
(one interface, ifindex 85 at that point)
MODIFICATIONS:
- Developer mode and SSH.
- Two of my own add-ons, neither of which creates or deletes network interfaces: a user-space VPN watchdog (it reconnects the VPN through ConnMan with net.connman.Service Disconnect/Connect when the route to the VPN server is lost) and a firewall kill switch (iptables raw-table rules that match
wg+). - A privacy-switch companion add-on. While the hardware privacy switch is on, it turns mobile data and the cellular radio off. It does not create or delete network interfaces.
ADDITIONAL INFORMATION:
Code path (GitHub - sailfishos/connman: Sailfish OS fork of https://01.org/connman · GitHub, tag 1.40+git26.6; these files are identical on master as of 2026-11-03):
connman/vpn/plugins/wireguard.c: the device is deleted only indisconnect()(wg_del_device(), line 1014, reached throughwg_disconnect()) and in thewg_connect()error path.struct vpn_driver(line 1076) has no.remove, andwg_exit()only callsvpn_unregister("wireguard").connman/vpn/main.c, lines 308–318: after SIGTERM the main loop quits, and__connman_plugin_cleanup()runs before__vpn_provider_cleanup().__connman_plugin_cleanup()callswg_exit(), which callsvpn_unregister()(connman/vpn/plugins/vpn.c, line 902), which callsvpn_provider_driver_unregister()(connman/vpn/vpn-provider.c, line 3603). For every WireGuard provider this only cancels agent requests and setsprovider->driver = NULL(line 3625). It does not disconnect the provider.__vpn_provider_cleanup()then callsclean_provider()(vpn-provider.c, line 3583), which seesprovider->driver == NULLand does not calldriver->remove. Even if it did,vpn_remove()callsstop_vpn()(vpn.c, line 75), which on purpose skipsdriver->disconnectforVPN_FLAG_NO_DAEMONdrivers (“Disconnect only VPNs with daemon”, lines 95–105). WireGuard isVPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON.- So
wg_del_device()is never called when connman-vpnd shuts down, and the wgN interface stays in the kernel. On the next connect,get_ifname()(wireguard.c, line 476) picks the firstwgNthat is not in vpnd’s interface list (vpn_ipconfig_foreach()). It skips the stalewg0and createswg1.
Impact:
- Routing and leaks: no direct effect, because the stale interface is down and has no routes.
- The tunnel name is not stable. Firewall rules, scripts or
wg show wg0that use the exact namewg0silently act on a dead interface after a restart. - The device is never deleted, so the kernel presumably still holds the configuration that
wg_set_device()gave it (private key, peer public key, endpoint) until reboot. Only root can read it. I did not dump it on the device. - Interfaces pile up: every restart or crash of connman-vpnd adds one more. In theory,
get_ifname()gives up afterwg255(“Failed to find an usable device name”). - Possibly related: in [Fixed] [5.2][Jolla Phone 2026] WireGuard VPN crashes other network connections,
wg0–wg3were left over after connmand crashes (post #6), and on 5.2.0.22 a connman crash loop left manywg#interfaces behind (post #21). This report shows the same leftover without any crash.
Workaround:
- Delete the stale interface as root, after checking that it is not the live tunnel (the live one is UP and is the default route device):
devel-su /usr/sbin/ip link del wg0. A reboot also clears it. - In scripts and firewall rules, match
wg+instead ofwg0, or look up the tunnel interface from the VPN connection’s interface index. A user-space watchdog and a firewall kill switch written this way keep working.
Possible fix: delete the WireGuard device on shutdown. For example, the WireGuard plugin could keep track of the devices it has created and call wg_del_device() for each of them in wg_exit(). A .remove callback alone would not be reached, see clean_provider() above. Another option is for vpn_provider_driver_unregister() to disconnect connected providers before it clears provider->driver. disconnect() already deletes the device synchronously (wg_del_device(), line 1014, a direct RTM_DELLINK netlink call). Only vpn_died() and freeing the plugin data are deferred to a 50 ms g_timeout (wg_died()), which no longer runs once the main loop has quit, so a shutdown fix must not depend on that timeout.
I also checked that the open PRs #144, #152, #155, #157, #158 and #159–#163 do not touch this path, and that the 5.2.0.20 → 5.2.0.22 changelog does not update the connman package. As of 2026-11-03, the newest commit on master is from 2026-10-12.