Skip to content

Fix network panel behind VPN policy routes - #8866

Open
hehh2001 wants to merge 2 commits into
omacom:quattrofrom
hehh2001:fix/network-status-physical-route
Open

hehh2001 wants to merge 2 commits into
omacom:quattrofrom
hehh2001:fix/network-status-physical-route

Conversation

@hehh2001

@hehh2001 hehh2001 commented Aug 29, 2026 •

Copy link
Copy Markdown

Summary

  • Prefer an active physical NetworkManager device (Wi-Fi / Ethernet) when the network panel picks the connection to describe
  • Fall back to the main routing table's lowest-metric default route when NetworkManager reports no active physical device
  • Keep the Network panel on the LAN interface even when a full-tunnel VPN or Tailscale exit node owns the default route / installs policy routing
  • Add/extend a regression test that simulates a connected Wi-Fi device alongside a connected Tailscale TUN device

Why

ip route get 1.1.1.1 follows policy routing. With a Tailscale exit node enabled it resolves to tailscale0, so the Network panel labels the connection as Ethernet and displays the Tailnet address instead of the active Wi-Fi/Ethernet address. Reading only the main routing table fixes policy-route VPNs but still follows a VPN that replaces the default route; preferring the active NetworkManager physical device covers both cases.

Verification

  • bash -n bin/omarchy-network-status test/shell.d/network-status-test.sh
  • test/shell.d/network-status-test.sh passes with stubbed nmcli/ip (connected Wi-Fi + connected tailscale0)
  • Live on Omarchy with an active Tailscale exit node: the panel shows the physical interface, local IPv4 address, and physical gateway

Copilot AI balanced review requested due to automatic review settings August 29, 2026 04:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates network detection to ignore VPN policy routes and retain the physical LAN interface.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Selects the lowest-metric default route from the main routing table.
  • Adds a regression test using stubbed network commands.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.

File Description
bin/omarchy-network-status Uses main-table routes for standard and verbose status.
test/shell.d/network-status-test.sh Tests physical-route selection behind policy routing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/shell.d/network-status-test.sh Outdated
Comment on lines +37 to +38
IP_CALLS="$test_tmp/ip-calls" PATH="$stub_bin:$ROOT/bin:$PATH" \
"$ROOT/bin/omarchy-network-status" --verbose >"$test_tmp/status"
Extend the VPN policy-route fix to prefer an active physical NetworkManager
device (wifi/ethernet) rather than only reading the main-table default route.
This keeps the network panel on the LAN connection even for full-tunnel VPNs
or Tailscale exit nodes that replace the default route.
@wundrellama

Copy link
Copy Markdown

Independent confirmation on a wg-quick-managed full-tunnel WireGuard connection (not WARP/Tailscale), plus a before/after capture against the current script.

Environment

  • Omarchy dev (86a2e583) / omarchy-dev 4.0.0.r2128.gb679363-1
  • Kernel 7.2.4-arch1-2, NetworkManager 1.58.1
  • Wi-Fi associated to an OWE-transition SSID; WireGuard tunnel AAM-WG with AllowedIPs = 0.0.0.0/0, ::/0 (owns the default route via table 51820)

Before (/usr/bin/omarchy-network-status --verbose, tunnel up):

iface   AAM-WG
ip      192.168.12.3
prefix  32
gateway
type    ethernet
cat: /sys/class/net/AAM-WG/speed: Invalid argument
speed
cat: /sys/class/net/AAM-WG/duplex: Invalid argument
duplex

After (this PR's bin/omarchy-network-status, same moment, tunnel still up):

iface   wlp5s0
ip      172.20.6.244
prefix  20
gateway 172.20.0.1
type    wifi
ssid    <redacted>-owe-tr
signal_dbm  -50
freq    5680.0
bitrate 286.7 MBit/s
router_ping_ms  1.45

The tunnel interface has no /sys/class/net/<iface>/wireless directory, so the
if wireless … else ethernet branch at the bottom of print_verbose classifies
it as ethernet, and the speed/duplex reads then fail with Invalid argument
because a WireGuard interface exposes neither. This PR's physical_device()
resolves it correctly.

One extra symptom worth recording: the bar icon and the panel's header text
are driven by different sources, so they disagree while a tunnel is up. The
text comes from info.type (this script, via detailsPoll), while the glyph
comes from kind in plugins/panels/network/Panel.qml, which is derived from
Quickshell's Networking.devices. The visible result is a header reading
"Ethernet" next to a Wi-Fi glyph, and because detailsPoll only runs while the
panel is open, the two drift in and out of agreement — which reads as the icon
flickering at random. Fixing the script's classification resolves the header
half; the glyph half is separate (Quickshell's DeviceType enum is
[None, Wifi, Wired], so a WireGuard device is reported as Wired and
Panel.qml's kind returns "ethernet" whenever a wired-type device is
connected).

A local workaround for the glyph half, for anyone hitting this before the
enum gains a VPN type:

# /etc/NetworkManager/conf.d/95-wireguard-unmanaged.conf
[keyfile]
unmanaged-devices=type:wireguard

wg-quick owns the interface anyway (NM only adopts it as
managed-type: 'external'), so marking it unmanaged costs nothing and stops
NM from racing the device through ~9 state transitions on every tunnel
up/down — each of which re-renders the bar icon.

@hehh2001

Copy link
Copy Markdown
Author

Thanks for the independent repro on a wg-quick full tunnel. A policy-routed default on table 51820 is exactly the case physical_device() exists for, and your before/after captures line up with what it does: pick the active physical NetworkManager device, and only fall back to the main table's lowest-metric default when there is none.

On the second symptom, you split it correctly and I agree it is two halves. The header text comes from info.type (this script), so this PR settles that half — a WireGuard interface is no longer classified as ethernet, and the speed/duplex reads that failed with Invalid argument go away with it. The glyph is drawn from kind in plugins/panels/network/Panel.qml, which derives it from Quickshell's Networking.devices; with DeviceType being [None, Wifi, Wired] a WireGuard device reports as Wired, so the panel can draw an Ethernet glyph next to a Wi-Fi header. I would rather not fold that into this PR: the honest fix is for the panel to make the same physical-device decision the status helper now makes (or for Quickshell to expose a VPN type) instead of inferring it from the device type, and that deserves its own change and review.

Your unmanaged-devices=type:wireguard drop-in is a good stopgap — wg-quick owns the interface anyway, so keeping NetworkManager out of it costs nothing. If you are willing to open an issue on omacom/omarchy with the flicker description, that is where the glyph half belongs; I will link it here so the two are read together.

@wundrellama

Copy link
Copy Markdown

Thanks. Issue #10619 already tracks the false-disconnected Wi-Fi icon, so I added our observations there rather than create a duplicate: #10619 (comment)

One correction to my earlier comment: the full-fan/slashed-fan cycling continued after unmanaged-devices=type:wireguard and a shell restart. That workaround did not resolve the flicker. The helper test supports the header fix in this PR, not a fix or proven cause for the intermittent icon changes. We did not test the device-state fix proposed in #10619.

@omarchybot

Copy link
Copy Markdown
Collaborator

Reviewed against quattro at 8af95b5. The bug is real: ip route get 1.1.1.1 follows policy routing, so a Tailscale exit node, a wg-quick full tunnel or a Clash TUN makes the panel describe the tunnel as Ethernet, and @wundrellama's before/after above shows this branch fixing it for wg-quick.

Four other open pull requests fix the same bug: #12071, #13529, #13666 and #13808. I read all five against quattro, and of them #12071 looks like the one to bring forward, for two reasons that bear on this branch:

  • physical_device() accepts only NetworkManager devices of type wifi or ethernet. When the uplink is a bridge or a bond, the first connected match is a port device with no address of its own (br0/bond0 are skipped), so the panel names the port and reports an empty IP. Show physical network behind TUN routes #12071 walks the main table's default routes and skips only tun and wireguard devices, so bridges, bonds and modems keep working.
  • When both Wi-Fi and Ethernet are up, the choice here rests on the order of nmcli dev status. That is the device holding the default route in the common case, but nmcli sorts a shared (hotspot) connection ahead of it, and when a NetworkManager VPN holds the default route the physical devices are ordered by how many addresses they have (nmc_active_connection_cmp in NetworkManager's src/nmcli/connections.c). Show physical network behind TUN routes #12071 takes the kernel's own lowest-metric order instead, as the base script did.

#13529 and #13808 read only the main table's default, so a NetworkManager VPN that installs its own default there (metric 50) is still shown as the uplink. #13666 also adds a vpn type and touches the panel, which is more than the bug needs.

Nothing was run on a worker for this branch, since it is not the fix I would bring forward. The second opinion (Codex Medium) did not run: its daily review budget was spent when this was checked, so this comparison is one model's reading and has not been checked by a second. Waiting on the maintainer to choose between the competing fixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants