-
Notifications
You must be signed in to change notification settings - Fork 0
Upstream ConnMan FD Leak & Automated Mitigation Guide
This wiki page documents a severe, systemic resource-tracking bug within the upstream ConnMan (connmand) network manager code layer, how it impacts LibreELEC appliances running WireGuard tunnels over Wi-Fi, and the automated optimizations deployed in WireGuard VPN Manager v1.5.3+ to permanently stabilize user deployments.
Older versions of connmand (up to version 2.0 under Linux Kernel 7.x/LibreELEC 13) contain structural resource-tracking bugs within their link-scanning and routing state machines. These bugs are triggered when out-of-band virtual network interfaces (such as standard kernel-space wg0 tunnels) alter default routing metrics.
Through exhaustive platform testing across Generic x86_64 and Raspberry Pi 4/5 profiles, we isolated a dangerous two-phase file descriptor leak:
- The Global Layer: An unconditional loop within ConnMan's internal DNS proxy and online reachability tracking engines that permanently orphans HTTP probes and socket handles during interface state changes.
-
The Wireless Layer (Severe Divergence): A persistent, compounding netlink (
nl80211) handle leak that is strictly Wi-Fi specific. On a hardwired Ethernet connection, file descriptor counts remain completely flat and stable. However, on Wi-Fi links, cycling or disconnecting the WireGuard tunnel causes an immediate, massive spike inconnmandfile descriptors, abandoning groups of 5 sequential socket inodes (or a massive surge of 72 FDs over an extended idle cycle) per event.
Because the default Linux kernel hard process ceiling allocates a maximum table threshold of 1024 open files, this aggressive accumulation eventually breaches the OS boundary. Once reached:
- The core operating system drops all active IP routing tables.
- Internet/local network connectivity drops completely.
- Inbound SSH terminal sessions and local user controls are killed instantly.
- The host system remains locked up until a hard power cycle is performed.
Telemetry captures verify that the leak survives even when user-space proxy routing and web connectivity verification routines are fully turned off. This shifts the failure vector directly to the Netlink event notification routines and interface link-state polling layers within ConnMan's underlying C source architecture (src/rtnl.c and plugins/wifi.c).
Every 1.0s: ls -l /proc/$(pidof connmand)/fd | wc -l
- VPN Connected Baseline (Stable over Wi-Fi): 18 FDs
- Immediately After Disconnect (Leak Trigger): 34 FDs
- Idle Sitting (Low-frequency creeping loop): 63 FDs and climbing!
When a routing switch occurs over wireless architecture, ConnMan instantiates netlink event notification routines or local telemetry tracking channels. In a GLib-based C architecture, these descriptors are commonly wrapped using:
GIOChannel *channel = g_io_channel_unix_new(fd);When an unmanaged interface (like wg0) drops abruptly, the state machine triggers an early return or a conditional error-handling bypass inside the event listener loop. Because this early exit path skips the mandatory cleanup sequences:
g_io_channel_unref(channel);close(fd);
The low-level socket descriptor remains completely orphaned and open in the active process table indefinitely.
To safeguard user environments from encountering system instability before an official upstream code fix is rolled into the core distribution, WGM v1.5.3+ automatically deploys a multi-tier, zero-intervention hardware optimization during initialization.
The addon silently provisions and locks down the following parameters directly within the storage partition:
The addon automatically overrides your core configuration file using strict case-sensitive camelCase keys. Standard lowercase entries are rejected by ConnMan's GLib parser, rendering them useless.
[General]
preferredtechnologies = ethernet,wifi,cellular
OnlineCheckMode = none
OnlineCheckIPv4URL =
OnlineCheckIPv6URL =
OnlineCheckInterval = 0
OnlineCheckURL = - Why: This completely shuts off ConnMan’s periodic internet accessibility probing subsystem and blocks it from attempting out-of-band WPAD/PAC proxy lookups, resolving infinite retry loops triggered by common home routers like Fritz!Box.
A systemd service drop-in override is automatically injected to re-sandbox the background daemon runtime parameters safely:
[Service]
ExecStart=
ExecStart=/usr/sbin/connmand -nr --config=/storage/.config/connman_main.conf --nodnsproxy
ExecStartPost=/bin/sh -c "sleep 2; if ! grep -q 'Method=manual' /storage/.cache/connman/*/settings 2>/dev/null; then echo -e 'nameserver 1.1.1.1\nnameserver 9.9.9.9' >> /etc/resolv.conf; fi"
LimitNOFILE=512
LogRateLimitIntervalSec=0-
--nodnsproxy: Runtime flag that completely bypasses ConnMan's corrupted internal DNS loop architecture. -
LimitNOFILE=512(The Native Circuit Breaker): Crucial Parameter Change. Setting this to a large value like4096is unsafe because it exceeds the OS unprivileged kernel default threshold (1024). Clamping this value strictly to 512 creates a secure runtime wrapper. If a user runs exclusively on Wi-Fi and cycles the VPN repeatedly, systemd will cleanly catch, terminate, and gracefully restart the daemon before it can reach the global OS exhaustion threshold, keeping local host networking and user terminal connections active. -
LogRateLimitIntervalSec=0: Prevents systemd from locking out or throttling diagnostic events during a rapid interface shift.
You can track your resource metrics live directly from your LibreELEC terminal window to confirm the optimization layer is actively running.
Run the following command to check the operational limit table boundaries for the running daemon process space:
cat /proc/$(pidof connmand)/limits | grep -E 'Max open files'Expected Secure Output:
Max open files 512 512 files
Query your journal to verify that the uppercase configuration settings were read properly by the parser engine:
journalctl -u connman -n 20 --no-pagerExpected Secure Entry Line:
connmand[xxxxxx]: Online check disabled; interface eth0 [ ethernet ] remains in ready state.
Run this active terminal monitor rule to track your connman-vpnd and connmand process handles side-by-side:
watch -n 1 "echo -n 'VPN Daemon (connman-vpnd): '; ls -l /proc/\$(pidof connman-vpnd)/fd 2>/dev/null | wc -l; echo -n 'Main Daemon (connmand): '; ls -l /proc/\$(pidof connmand)/fd 2>/dev/null | wc -l"Under optimized conditions, even after extensive interface switching loops, your metrics should remain completely flatlined within a healthy operational baseline profile:
VPN Daemon (connman-vpnd): 10
Main Daemon (connmand): 19 (to 29 max depending on Pi 4/5 hardware links)
A: Because the upstream bug is located within ConnMan’s wireless-specific link-state tracking loops (plugins/wifi.c). When operating over Ethernet, those specific code routes are bypassed. On Wi-Fi, however, every VPN interface shift triggers unreleased Netlink and dynamic polling socket allocations that permanently accumulate.
A: No. If the file descriptor limit is hit on a wireless connection, systemd recycles the connmand background service in less than a second. Kodi and active video streams will not crash. At worst, you might see a brief 1-second pause in network traffic before the system automatically self-heals.
A: ConnMan uses a strict, case-sensitive configuration parser. If you use lowercase strings, ConnMan completely ignores them as invalid configuration lines and secretly falls back to its default background polling behaviors. It must be written as OnlineCheckMode = none.
A: No. LibreELEC is a read-only appliance, but it provides a dedicated writable configuration directory at /storage/.config/. WGM installs its optimizations safely inside this user space, ensuring your core system partition remains completely untouched and safe for future LibreELEC system updates.
A: No, this mitigation is built to be forward-compatible. If the upstream developers release a patched connmand binary that fixes the leak, our explicit camelCase overrides (OnlineCheckMode = none) and --nodnsproxy parameters will continue to act as excellent privacy and performance enhancements by stopping unnecessary background tracking telemetry.
A: Yes. WGM injects a safe ExecStartPost fallback routing hook into the service template. It automatically checks your network configuration folder—if you haven't assigned manual nameservers, it safely maps fallback public gateways (1.1.1.1 / 9.9.9.9) to guarantee your media box never suffers from DNS resolution dropouts when the VPN cycles.
For developers building custom LibreELEC images or working directly with upstream repository source targets, this section maps the exact C-code refactoring patch submitted to the core maintainers (connman@lists.linux.dev).
When an out-of-band kernel virtual interface state change triggers structural notification message sweeps (RTM_DELLINK / RTM_NEWLINK), ConnMan executes internal state mapping routines inside src/rtnl.c. If wireless channels are active, interface tracking handles map directly to plugins/wifi.c.
The root cause of the handle accumulation sits directly within the conditional exit gates of interface_removed() inside plugins/wifi.c:
static void interface_removed(GSupplicantInterface *interface)
{
// ...
if (wifi == NULL || wifi->device == NULL) {
DBG("wifi interface already removed");
return; /* 🛑 STRUCTURAL RESOUCE HOLE: Early return bypasses resource teardown! */
}
wifi->interface = NULL;
connman_device_set_powered(wifi->device, FALSE);
}If the device linkages or interface arrays are unlinked or modified out-of-band before this execution thread completes, the function hits the early return check. This structural bypass skips the mandatory unreferencing blocks (g_supplicant_interface_cancel), leaving open tracking handles running infinitely inside the system handle space.
The following patch refactors plugins/wifi.c to separate hardware link drops from object allocation lifecycles, ensuring descriptor resource cancellation executes unconditionally during a non-standard tunnel teardown event window.
From: Doemela <kasuta@riseup.net>
Date: Sat, 15 Aug 2026 16:40:00 +0200
Subject: [PATCH] plugins/wifi: Fix GSupplicantInterface socket leak on out-of-band drop
When an unmanaged or virtual interface (like WireGuard) changes state or drops,
`interface_removed()` is invoked within the wireless event pathways.
If `wifi` or `wifi->device` has already been cleared or unlinked during the
topology shift, the function triggers an early return. This conditional block
accidentally bypasses the mandatory `g_supplicant_interface_cancel()` and
data unreferencing routines. As a result, low-level netlink and event
file descriptors are permanently leaked in the process table.
This patch refactors `interface_removed()` to ensure that the core supplicant
interface resource cancellation runs unconditionally before releasing control.
Signed-off-by: Doemela <kasuta@riseup.net>
---
diff --git a/plugins/wifi.c b/plugins/wifi.c
index 9ce7b5a..bcf8321 100644
--- a/plugins/wifi.c
+++ b/plugins/wifi.c
@@ -1014,14 +1014,16 @@ static void interface_removed(GSupplicantInterface *interface)
wifi = g_supplicant_interface_get_data(interface);
if (wifi != NULL && wifi->tethering == TRUE)
return;
- if (wifi == NULL || wifi->device == NULL) {
- DBG("wifi interface already removed");
- return;
- }
+ if (wifi != NULL && wifi->device != NULL) {
+ wifi->interface = NULL;
+ connman_device_set_powered(wifi->device, FALSE);
+ } else {
+ DBG("wifi device linkage missing, executing isolated interface cleanup");
+ }
- wifi->interface = NULL;
- connman_device_set_powered(wifi->device, FALSE);
+ g_supplicant_interface_set_data(interface, NULL);
+ g_supplicant_interface_cancel(interface);
}If you are compiling your own custom build target using the LibreELEC.tv compilation framework system, you can inject this fix directly into the package structure using these execution rules:
- Create a tracking directory within your custom build tree:
mkdir -p packages/network/connman/patches/ - Save the patch file above into that directory as:
packages/network/connman/patches/connman-999-fix-wifi-fd-leak.patch - Trigger a fresh rebuild of the package architecture matching your specific target hardware profile:
For PC / Intel / AMD (Generic x86_64 Target):
PROJECT=Generic ARCH=x86_64 ./scripts/clean connman
PROJECT=Generic ARCH=x86_64 ./scripts/build connmanFor Raspberry Pi 4 (32-bit ARM Target):
PROJECT=RPi ARCH=arm ./scripts/clean connman
PROJECT=RPi ARCH=arm ./scripts/build connmanFor Raspberry Pi 5 (64-bit AArch64 Target):
PROJECT=RPi ARCH=aarch64 ./scripts/clean connman
PROJECT=RPi ARCH=aarch64 ./scripts/build connmanThe build environment will automatically scan, stitch, and bind your localized structural refactoring patch straight into the compiled binary!
Created by Doemela