Overview
I spent way too much time wrangling the NetworkManager iodine plugin on Ubuntu/Debian, only to discover it refused to shut down properly after disconnecting. This blog post details the steps I took—from frustration to elation—when I finally pinned down the cause and wrote a very small patch to fix it for good.
Symptoms
The issues I encountered were frustratingly consistent:
- First connection to the iodine VPN worked (most of the time).
- Disconnect left behind a process that refused to die.
- Second connection attempt hung or timed out.
The culprit? The plugin's D-Bus service was sticking around in some zombie-like form because it never cleanly exited its main loop.
Key Clues
During my debugging process, several patterns emerged:
nmcli connection up/down
commands would hang if I tried to connect again.ps aux | grep iodine
revealed the plugin process was still alive afterDisconnect
.dbus-monitor --system "interface='org.freedesktop.NetworkManager.VPN.Plugin'"
logs showed no final "quit" or "StateChanged(6)" signals from the plugin.
Meanwhile, I wrote a simple Python plugin that:
- Launched
iodine -f
in a subprocess, - Captured stdout/stderr to parse IP info,
- Sent an
Ip4Config
signal to let NetworkManager configure the interface, - And exited with
g_main_loop_quit()
afterDisconnect
.
That Python approach worked flawlessly for multiple connect/disconnect cycles.
Digging into the C Plugin
The Original Code
The existing network-manager-iodine
code is in C, hooking into older style NetworkManager VPN APIs. I noticed that while it handled launching the iodine
binary, it never actually exited after the user disconnected. Instead, it would kill the iodine child process—but the plugin's own main loop stuck around, so from NetworkManager's perspective, the D-Bus service was still present and "half-connected."
The D-Bus Logs
Comparing logs for the Python version and the C version revealed the issue:
- Python plugin: Emitted something like
StateChanged(6)
("plugin shutting down"), then it disappeared from D-Bus. - C plugin: Emitted a few final signals but never a "quit" or any direct call to kill the main loop. The main process lingered.
Hence the second connection attempt hammered on a stale plugin instance.
The One-Line Fix
I discovered the C plugin was already set up to exit on the "quit"
signal:
g_signal_connect (plugin, "quit",
G_CALLBACK (quit_mainloop),
main_loop);
However, that "quit" signal was never emitted. So in the real_disconnect()
function, I added:
g_signal_emit_by_name(plugin, "quit");
That triggers the plugin's quit_mainloop()
callback, which calls g_main_loop_quit()
. Once the main loop quits, the plugin process truly dies, cleaning up from D-Bus. Voila, the second connection attempt now re-launches a fresh plugin. Problem solved!
The Final Patch
Here's the complete patch that fixes the issue:
--- a/src/nm-iodine-service.c
+++ b/src/nm-iodine-service.c
@@ -294,6 +294,7 @@ real_disconnect (NMVpnServicePlugin *plugin, GError **err)
else
kill (priv->pid, SIGKILL);
+ g_signal_emit_by_name (plugin, "quit");
g_message ("Terminated iodine daemon with PID %d.", priv->pid);
priv->pid = 0;
}
You can apply this patch to the Debian/Ubuntu packaging or your local build and recompile with:
$ sudo apt-get install build-essential libnm-dev \
libnm-glib-dev libnm-util-dev libdbus-glib-1-dev \
libglib2.0-dev
$ patch -p1 < fix-nm-iodine-plugin.patch
$ ./configure --prefix=/usr
$ make
$ sudo make install
(Adjust dependencies/paths as needed for your environment.)
Verifying the Fix
To test that the fix works properly:
- Connect with
nmcli connection up my-iodine-tunnel
. - Disconnect with
nmcli connection down my-iodine-tunnel
. Now the plugin process should exit. - Connect again. No stale state! It should come right up.
To see the new events in real time:
$ dbus-monitor --system "interface='org.freedesktop.NetworkManager.VPN.Plugin'"
Watch for the plugin's final signals and confirm it cleanly disappears.
Conclusion
If your network-manager-iodine plugin kept "haunting" your system after disconnection, this patch is for you.
The main takeaway: always ensure your plugin tears down the GMainLoop. Otherwise, you'll see stale processes that hamper future connections.
My Python plugin test confirmed the theory quickly, and once I saw the difference in D-Bus signals, the fix was straightforward.
Now, Haxinator 2000 has a rock-solid, multi-connect-capable NetworkManager iodine plugin. Grab the patch and never fear a second connection attempt again!
Happy Tunneling!