OpenVPN 2.7 tun detection and bypass routing on modern iproute2 #61

Merged
mysticalsoap merged 3 commits from mysticalsoap/fix/openvpn27-tun-and-bypass-route into trunk 2026-08-19 13:05:35 -04:00 AGit
Owner

Problem

Live-testing #58 surfaced three inherited bugs, found via one service log. Together they made the bypass silently ineffective (a bypassed shell showed the VPN's egress IP) and froze the status widget at "Uptime: 01s" with no traffic stats.

  1. OpenVPN 2.7 broke tun-device detection. The log parser looks for TUN/TAP device, but 2.7 with DCO prints DCO device tun0 opened / ovpn-dco device [tun0] opened — and 2.7 also switched to ISO log timestamps, which broke the old asctime-strip + position-based split even for the classic message. self.tun stayed None: no per-link DNS (DNS: systemd-resolved is active and no interface was given), and the frozen status widget (TunnelMon dies on its first stat read of a nonexistent interface).
  2. Bypass routing dies on modern iproute2. create_cgroup added the same ip rule twice; iproute2 now refuses an exact-duplicate rule ("File exists"), the exception aborts the block, and the default route never lands in table 11 — marked packets fall through to main and out the tunnel. Duplicate copies also accumulated across reconnects because deletion removed only one (and never the v6 rule at all).
  3. return_tun_device crashed on None — a 's'-signature D-Bus method can't marshal it (Unable to append (None,) in the log), so the GUI got an error instead of an answer.

Fix

  • tunnel.parse_tun_device(): regex over the raw line matching all three announcement forms, timestamp-format agnostic
  • bypass.clear_fwmark_rules(ip6=...): deletes every copy of the fwmark rule, called before the now-single ip rule add in create_cgroup, in delete_cgroup (which gains the previously-missing v6 cleanup), and in the WireGuard reconnect block; the v6 log lines also stop printing the v4 gateway
  • return_tun_device returns "" when no device is set

Verification

  • New tests: all three OpenVPN announcement formats + GUI-prefixed line + non-match; clear_fwmark_rules deletes until none remain and addresses the right stack (57 pass)
  • CI runs pytest/ruff
  • Live check (OpenVPN + bypass): status widget counts up with live rates; ip rule shows exactly one fwmark rule; ip route show table 11 has the physical default route; curl icanhazip.com from a bypassed shell prints the home IP

Related fixes: found while live-testing #58; with routing fixed, the DNS path verified there becomes effective end to end.

Assisted-by: claude-fable-5

## Problem Live-testing #58 surfaced three inherited bugs, found via one service log. Together they made the bypass silently ineffective (a bypassed shell showed the VPN's egress IP) and froze the status widget at "Uptime: 01s" with no traffic stats. 1. **OpenVPN 2.7 broke tun-device detection.** The log parser looks for `TUN/TAP device`, but 2.7 with DCO prints `DCO device tun0 opened` / `ovpn-dco device [tun0] opened` — and 2.7 also switched to ISO log timestamps, which broke the old `asctime`-strip + position-based split even for the classic message. `self.tun` stayed `None`: no per-link DNS (`DNS: systemd-resolved is active and no interface was given`), and the frozen status widget (`TunnelMon` dies on its first stat read of a nonexistent interface). 2. **Bypass routing dies on modern iproute2.** `create_cgroup` added the same `ip rule` twice; iproute2 now refuses an exact-duplicate rule ("File exists"), the exception aborts the block, and the default route never lands in table 11 — marked packets fall through to main and out the tunnel. Duplicate copies also accumulated across reconnects because deletion removed only one (and never the v6 rule at all). 3. **`return_tun_device` crashed on `None`** — a `'s'`-signature D-Bus method can't marshal it (`Unable to append (None,)` in the log), so the GUI got an error instead of an answer. ## Fix - `tunnel.parse_tun_device()`: regex over the raw line matching all three announcement forms, timestamp-format agnostic - `bypass.clear_fwmark_rules(ip6=...)`: deletes every copy of the fwmark rule, called before the now-single `ip rule add` in `create_cgroup`, in `delete_cgroup` (which gains the previously-missing v6 cleanup), and in the WireGuard reconnect block; the v6 log lines also stop printing the v4 gateway - `return_tun_device` returns `""` when no device is set ## Verification - New tests: all three OpenVPN announcement formats + GUI-prefixed line + non-match; `clear_fwmark_rules` deletes until none remain and addresses the right stack (57 pass) - CI runs pytest/ruff - Live check (OpenVPN + bypass): status widget counts up with live rates; `ip rule` shows exactly one fwmark rule; `ip route show table 11` has the physical default route; `curl icanhazip.com` from a bypassed shell prints the home IP Related fixes: found while live-testing #58; with routing fixed, the DNS path verified there becomes effective end to end. Assisted-by: claude-fable-5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: an unset tun device cannot cross the bus as a string
All checks were successful
ci / test (pull_request) Successful in 46s
33d905974f
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mysticalsoap force-pushed mysticalsoap/fix/openvpn27-tun-and-bypass-route from 33d905974f
All checks were successful
ci / test (pull_request) Successful in 46s
to d62a50a45a
All checks were successful
ci / test (pull_request) Successful in 40s
ci / test (push) Successful in 47s
2026-08-19 13:03:01 -04:00
Compare
Sign in to join this conversation.
No description provided.