You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[macOS] Kill-switch blocks all egress on non-RFC1918 LANs; pinholes not re-installed when primary interface changes (Wi-Fi ↔ Ethernet)
Summary
On macOS, NymVPN v2.23.2 with the (hardcoded "Always on") kill-switch and the Bypass LAN toggle enabled fails to function on a LAN that uses a non-RFC1918 address range delegated by a secondary router. Two distinct but related issues compound:
Bypass LAN's whitelist is RFC1918-only and not user-configurable. Any DHCP-assigned address outside 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 falls through to the anchor's final block drop quick all rule, including ICMP/ARP-followup probes from the macOS reachability subsystem (SCNetworkReachability / "CrazyIvan46"). The macOS kernel then marks the LAN gateway "Not Reachable" and refuses to install a default route via that interface.
Pinhole routes to the WireGuard entry gateways are not re-installed when the primary interface changes live. After connecting via Wi-Fi, the daemon installs /32 host routes to the entry IPs pointing at the Wi-Fi gateway. If the user later activates a USB-Ethernet adapter on a different LAN and the new adapter becomes Service Create LICENSE #1, the daemon does not regenerate those pinholes for the new underlay. Combined with the kill-switch (which keeps block drop quick all in PF regardless of app connection state), all egress halts.
Together these create a catch-22: the daemon can't bring the tunnel up over the new underlay because DNS / control-plane traffic is blocked by the very kill-switch that's waiting for the tunnel.
This appears related to but distinct from #2288 (closed, feature request) and #5244 (closed, sleep/wake variant of the same kill-switch design). The novel piece here is the live underlay-switch failure mode on non-RFC1918 LANs.
Environment
App: NymVPN.app v2.23.2 (nym-vpn-lib matches the version published 2026-05-26)
Mode: 2-hop WireGuard, kill-switch ("Always on"), Bypass LAN ON, IPv6 ON
Underlays in scope:
Wi-Fi en0, ISP router on an RFC1918 LAN (e.g. 192.168.x.x)
USB-Ethernet adapter (e.g. ASIX AX88179-based dock) as en5, plugged into a secondary router that hands out addresses in a non-RFC1918 range (the user's "isolated LAN") with its own gateway. Example used here: documentation range like 203.0.113.0/24 with gateway 203.0.113.1 — in the field this can be any operator-chosen prefix outside the three RFC1918 blocks.
Reproduction
Connect NymVPN over Wi-Fi. Tunnel comes up cleanly; PF anchor nym is populated (~67 rules ending in block drop quick all).
Plug in a USB-Ethernet adapter whose DHCP server (a secondary router under your control) hands out IPs in a non-RFC1918 range, e.g. 203.0.113.42/24 with router 203.0.113.1. The adapter becomes a network service; depending on Service Order it may immediately become Create LICENSE #1.
From the macOS network stack's point of view, en5 is now the primary IPv4 interface and the kernel attempts reachability probes against 203.0.113.1.
Observe: the probes never leave the interface. tcpdump -i en5 captures incoming ARP/IGMP/NetBIOS from the new gateway and the Mac's ARP replies, but zero ICMP echo-request frames are transmitted, even when the user invokes ping -b en5 203.0.113.1 directly. PF counter on block drop quick all increments by exactly the number of attempted outbound packets.
route -n get -ifscope en5 203.0.113.1 initially shows <UP,HOST,DONE,LLINFO,WASCLONED,IFSCOPE,IFREF,ROUTER> then, within a couple of seconds, the ROUTER flag disappears. IPMonitor logs (subsystem == com.apple.SystemConfiguration) show reach: 0x00000000 (Not Reachable) against if_index N (en5) and the kernel demotes the IPv6 over the prior primary.
The tunnel utun8 loses its underlay (the Wi-Fi default is also demoted). Disconnecting the app does not restore connectivity — the daemon keeps the anchor populated 24/7 (kill-switch design).
The only paths back to a working state are: (a) disable the new Ethernet service, or (b) sudo launchctl bootout system/net.nymtech.vpn.daemon.
Expected behaviour
The kill-switch should not silently block reachability probes to the LAN gateway of any interface providing connectivity. Either:
Auto-whitelist the DHCP-assigned LAN subnet and gateway for each network service (not just RFC1918), at least for ICMP echo / ARP / ND6 / DHCP / link-local — or
When the primary interface changes, the daemon should:
Tear down stale entry-gateway /32 pinholes pointing at the old gateway, and
Install fresh pinholes via the new gateway, or
At minimum, briefly suspend the catch-all block so the new underlay can come up and the control plane (DNS, entry-gateway TCP/UDP) can flow.
Actual behaviour
block drop quick all (the final rule of anchor nym) accumulates packets at ~100/s while the new Ethernet service is active. Confirmed by reading pfctl -a nym -vsr.
The macOS kernel never installs a usable default route via the new interface because reachability fails.
nym-vpnd does not regenerate entry-gateway pinholes after primary changes.
No daemon log entries indicate that the failure is being detected.
Evidence (sanitized excerpts)
# PF anchor counter after ~30 min of attempted use with Ethernet primary
block drop quick all
[ Evaluations: 404765 Packets: 404765 Bytes: ~400 MB States: 0 ]
# tcpdump on en5 during ping attempt — only inbound from gateway, no ICMP out
<gateway-mac> > <mac-mac>: ARP Request who-has <mac-ip>
<mac-mac> > <gateway-mac>: ARP Reply <mac-ip> is-at <mac-mac>
... no ICMP echo request frames ...
# Route flags lose ROUTER after ~3s
route to: <gateway>
flags: <UP,HOST,DONE,LLINFO,WASCLONED,IFSCOPE,IFREF,ROUTER> ← initially
flags: <UP,HOST,DONE,LLINFO,WASCLONED,IFSCOPE,IFREF> ← after probes drop
# Validated workaround: with daemon stopped, the same Ethernet primary works fully
ping <gateway> : 100% received
ping 8.8.8.8 : 100% received
curl https://api.ipify.org : HTTP 200
Workarounds in use
Locally we ship two scripts (paraphrased):
nym-cable-on: launchctl bootout the daemon, pfctl -a nym -F all, reorder Service Order to put the Ethernet adapter first, enable it. Daemon stays stopped — Internet flows directly via the new LAN, without VPN.
nym-cable-off: stop daemon, reorder to Wi-Fi first, disable Ethernet, launchctl bootstrap the daemon, open the app for manual Connect.
These are pragmatic but defeat the purpose of running NymVPN at the user's primary workspace.
Proposed fixes (in order of impact)
User-configurable Bypass LAN allowlist accepting CIDR (revisit Allow Access To LAN When Firewall/Kill Switch Is Enabled Setting #2288). When enabled, install pass in/out quick inet from any to <cidr> rules in the anchor ahead of the catch-all block. This alone unblocks the reachability probes and lets non-RFC1918 LANs work.
Auto-bypass the current DHCP-assigned LAN of every active service for the minimal protocols required by reachability detection (ICMP echo, ARP, ND6, DHCP). This avoids users having to configure anything.
Re-install entry-gateway pinholes on primary-interface change. Subscribe to State:/Network/Global/IPv4 via SCDynamicStore (or NEHelper events) and regenerate /32 host routes accordingly.
Optionally provide a "kill-switch: off" toggle in the UI (or at least a "soft" mode that lifts the catch-all block when the user explicitly disconnects). Today the kill-switch is hardcoded "Always on" without a UI control.
[macOS] Kill-switch blocks all egress on non-RFC1918 LANs; pinholes not re-installed when primary interface changes (Wi-Fi ↔ Ethernet)
Summary
On macOS, NymVPN v2.23.2 with the (hardcoded "Always on") kill-switch and the Bypass LAN toggle enabled fails to function on a LAN that uses a non-RFC1918 address range delegated by a secondary router. Two distinct but related issues compound:
Bypass LAN's whitelist is RFC1918-only and not user-configurable. Any DHCP-assigned address outside
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16falls through to the anchor's finalblock drop quick allrule, including ICMP/ARP-followup probes from the macOS reachability subsystem (SCNetworkReachability/ "CrazyIvan46"). The macOS kernel then marks the LAN gateway "Not Reachable" and refuses to install a default route via that interface.Pinhole routes to the WireGuard entry gateways are not re-installed when the primary interface changes live. After connecting via Wi-Fi, the daemon installs
/32host routes to the entry IPs pointing at the Wi-Fi gateway. If the user later activates a USB-Ethernet adapter on a different LAN and the new adapter becomes Service Create LICENSE #1, the daemon does not regenerate those pinholes for the new underlay. Combined with the kill-switch (which keepsblock drop quick allin PF regardless of app connection state), all egress halts.Together these create a catch-22: the daemon can't bring the tunnel up over the new underlay because DNS / control-plane traffic is blocked by the very kill-switch that's waiting for the tunnel.
This appears related to but distinct from #2288 (closed, feature request) and #5244 (closed, sleep/wake variant of the same kill-switch design). The novel piece here is the live underlay-switch failure mode on non-RFC1918 LANs.
Environment
nym-vpn-libmatches the version published 2026-05-26)system/net.nymtech.vpn.daemon(LaunchDaemon, root)en0, ISP router on an RFC1918 LAN (e.g.192.168.x.x)en5, plugged into a secondary router that hands out addresses in a non-RFC1918 range (the user's "isolated LAN") with its own gateway. Example used here: documentation range like203.0.113.0/24with gateway203.0.113.1— in the field this can be any operator-chosen prefix outside the three RFC1918 blocks.Reproduction
nymis populated (~67 rules ending inblock drop quick all).203.0.113.42/24with router203.0.113.1. The adapter becomes a network service; depending on Service Order it may immediately become Create LICENSE #1.en5is now the primary IPv4 interface and the kernel attempts reachability probes against203.0.113.1.tcpdump -i en5captures incoming ARP/IGMP/NetBIOS from the new gateway and the Mac's ARP replies, but zero ICMP echo-request frames are transmitted, even when the user invokesping -b en5 203.0.113.1directly. PF counter onblock drop quick allincrements by exactly the number of attempted outbound packets.route -n get -ifscope en5 203.0.113.1initially shows<UP,HOST,DONE,LLINFO,WASCLONED,IFSCOPE,IFREF,ROUTER>then, within a couple of seconds, theROUTERflag disappears.IPMonitorlogs (subsystem == com.apple.SystemConfiguration) showreach: 0x00000000 (Not Reachable)againstif_index N (en5)and the kernel demotes the IPv6 over the prior primary.utun8loses its underlay (the Wi-Fi default is also demoted). Disconnecting the app does not restore connectivity — the daemon keeps the anchor populated 24/7 (kill-switch design).sudo launchctl bootout system/net.nymtech.vpn.daemon.Expected behaviour
/32pinholes pointing at the old gateway, andActual behaviour
block drop quick all(the final rule of anchornym) accumulates packets at ~100/s while the new Ethernet service is active. Confirmed by readingpfctl -a nym -vsr.nym-vpnddoes not regenerate entry-gateway pinholes after primary changes.Evidence (sanitized excerpts)
Workarounds in use
Locally we ship two scripts (paraphrased):
nym-cable-on:launchctl bootoutthe daemon,pfctl -a nym -F all, reorder Service Order to put the Ethernet adapter first, enable it. Daemon stays stopped — Internet flows directly via the new LAN, without VPN.nym-cable-off: stop daemon, reorder to Wi-Fi first, disable Ethernet,launchctl bootstrapthe daemon, open the app for manual Connect.These are pragmatic but defeat the purpose of running NymVPN at the user's primary workspace.
Proposed fixes (in order of impact)
pass in/out quick inet from any to <cidr>rules in the anchor ahead of the catch-all block. This alone unblocks the reachability probes and lets non-RFC1918 LANs work.State:/Network/Global/IPv4via SCDynamicStore (or NEHelper events) and regenerate/32host routes accordingly.(1) + (3) together would fix our case completely.
Related
nym-firewallcrate)Happy to provide additional sanitized logs, daemon traces, or to test patches on a 2-hop WireGuard build.