The utun6 row is a tunnel and already ignored. The bridge100 row is installed by vmnet for the VM subnet. It is interface-scoped (I) and has no next-hop gateway: the gateway column is the link itself (link#45, no G flag). Nothing beyond the bridge's own subnet is reachable through it, so it is not an uplink.
Scoped default routes cannot simply be dropped: while a VPN holds the default route, the physical interface keeps only a default route scoped to itself (default 192.168.1.1 UGScIg en0), and that is how the physical pane is found in the VPN case.
Fix
A physical default route on macOS now requires a next-hop gateway (flag G). The physical interface's default always has one, scoped or not; the on-link pseudo-defaults from vmnet and Internet Sharing never do. parseNetstat records a gateway only when G is set.
Verification
make test, make fmt, make lint clean.
New TestSelectDarwinVMBridge covers the bridge row next to the physical default, next to a connected VPN, and alone (error).
Built and run on the affected Mac: detection now logs Monitoring 1 interface(s) and picks en0.
README detection rules updated.
No tracked issue: issue creation was not permitted from the session that made this change.
## Problem
On a Mac running a VM with NAT networking (vmnet / Internet Sharing), `rtnetmon` refuses to start:
```
Error: multiple physical default-route interfaces found; rtnetmon supports only one (bridge100, en0)
```
`netstat -rn -f inet` has three `default` rows:
```
default 10.100.205.254 UGScg en0
default link#31 UCSIg utun6
default link#45 UCSIg bridge100 !
```
The `utun6` row is a tunnel and already ignored. The `bridge100` row is installed by vmnet for the VM subnet. It is interface-scoped (`I`) and has no next-hop gateway: the gateway column is the link itself (`link#45`, no `G` flag). Nothing beyond the bridge's own subnet is reachable through it, so it is not an uplink.
Scoped default routes cannot simply be dropped: while a VPN holds the default route, the physical interface keeps only a default route scoped to itself (`default 192.168.1.1 UGScIg en0`), and that is how the physical pane is found in the VPN case.
## Fix
A physical default route on macOS now requires a next-hop gateway (flag `G`). The physical interface's default always has one, scoped or not; the on-link pseudo-defaults from vmnet and Internet Sharing never do. `parseNetstat` records a gateway only when `G` is set.
## Verification
- `make test`, `make fmt`, `make lint` clean.
- New `TestSelectDarwinVMBridge` covers the bridge row next to the physical default, next to a connected VPN, and alone (error).
- Built and run on the affected Mac: detection now logs `Monitoring 1 interface(s)` and picks `en0`.
- README detection rules updated.
No tracked issue: issue creation was not permitted from the session that made this change.
A VM with NAT networking makes vmnet install a default route scoped to
its bridge (bridge100) with no gateway. The detector counted it as a
second physical default route and refused to start:
multiple physical default-route interfaces found (bridge100, en0)
Scoped routes cannot simply be dropped: while a VPN holds the default
route, the physical interface keeps only a default route scoped to
itself. That route still names its gateway (flag G). The on-link one
does not, so a physical default route now requires a next-hop gateway.
parseNetstat records a gateway only when the G flag is set; a link#N
gateway column is the interface itself, not a next hop.
sneak
merged commit 053c00cd52 into next2026-11-07 05:23:31 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
On a Mac running a VM with NAT networking (vmnet / Internet Sharing),
rtnetmonrefuses to start:netstat -rn -f inethas threedefaultrows:The
utun6row is a tunnel and already ignored. Thebridge100row is installed by vmnet for the VM subnet. It is interface-scoped (I) and has no next-hop gateway: the gateway column is the link itself (link#45, noGflag). Nothing beyond the bridge's own subnet is reachable through it, so it is not an uplink.Scoped default routes cannot simply be dropped: while a VPN holds the default route, the physical interface keeps only a default route scoped to itself (
default 192.168.1.1 UGScIg en0), and that is how the physical pane is found in the VPN case.Fix
A physical default route on macOS now requires a next-hop gateway (flag
G). The physical interface's default always has one, scoped or not; the on-link pseudo-defaults from vmnet and Internet Sharing never do.parseNetstatrecords a gateway only whenGis set.Verification
make test,make fmt,make lintclean.TestSelectDarwinVMBridgecovers the bridge row next to the physical default, next to a connected VPN, and alone (error).Monitoring 1 interface(s)and picksen0.No tracked issue: issue creation was not permitted from the session that made this change.
A VM with NAT networking makes vmnet install a default route scoped to its bridge (bridge100) with no gateway. The detector counted it as a second physical default route and refused to start: multiple physical default-route interfaces found (bridge100, en0) Scoped routes cannot simply be dropped: while a VPN holds the default route, the physical interface keeps only a default route scoped to itself. That route still names its gateway (flag G). The on-link one does not, so a physical default route now requires a next-hop gateway. parseNetstat records a gateway only when the G flag is set; a link#N gateway column is the interface itself, not a next hop.