docs(networking): WFP IPFORWARD egress is a dead end on WinNAT (negative result + evidence) - #143
Open
luthermonson wants to merge 1 commit into
Open
docs(networking): WFP IPFORWARD egress is a dead end on WinNAT (negative result + evidence)#143luthermonson wants to merge 1 commit into
luthermonson wants to merge 1 commit into
Conversation
…nce) Windows container egress cannot be enforced by a host-side WFP filter on the Server 2025 HNS NAT + Hyper-V-isolated stack. Built and drove a pure-Go tailscale/wf spike on the live node: a BLOCK filter at FWPM_LAYER_IPFORWARD_V4 scoped to the NAT vSwitch arrival interface (and destination) does not drop the container's forwarded/SNAT'd egress. Neither does INBOUND_IPPACKET_V4 (arrival scoped or not) nor a dest-only block at OUTBOUND_IPPACKET_V4 post-NAT. Positive controls prove WFP is enforced on the host (blocking the host's own 1.1.1.1 at ALE and OUTBOUND both worked), so the container's packets travel the Hyper-V vSwitch/HNS datapath out-of-band of the host tcpip.sys WFP hooks (Get-NetNat empty, per-interface Forwarding Disabled). This closes a fifth mechanism after the four prior host-side attempts; the fix for this platform is network-level (isolated VLAN denying RFC1918), not a daemon-side filter. Adds docs/arch/windows-egress-wfp-investigation.md with the full method, containment logs, positive controls, and a reproducer, and records the finding in firewall_windows.go so no sixth attempt starts from scratch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A negative result, proven on the live Windows node, plus the evidence and a reproducer. Windows container egress cannot be enforced by a host-side WFP filter on this Server 2025 HNS NAT + Hyper-V-isolated stack. This closes a fifth mechanism after four prior host-side failures, and it means the fix for the Windows platform is network-level (an isolated VLAN), not the daemon.
No daemon behavior changes: this PR adds
docs/arch/windows-egress-wfp-investigation.mdand records the finding infirewall_windows.goso nobody starts a sixth attempt from scratch.The mechanism that was tested
The Linux analogue of ephemerd's
FORWARD-chain block (firewall_linux.go) is a WFP BLOCK filter atFWPM_LAYER_IPFORWARD_V4— no callout driver, no kernel signing. Built and driven onmfl-win-amd64-101via the pure-Gogithub.com/tailscale/wfbinding. Scoping was by the ephemerd NAT vSwitch arrival interface (robust to pre/post-SNAT) with an RFC1918 destination allow-list carving out the container subnet.Field mapping had to be corrected against the live layer: on this build
IPFORWARD_V4exposes noIP_ARRIVAL_INTERFACE— the arrival interface isIP_LOCAL_INTERFACEand the destination isIP_DESTINATION_ADDRESS.Why the prior four failed (context)
localip=10.88/16(sec(networking): host-firewall egress backstop for Windows job containers #136) — WinNAT rewrites source before host ALE; also host-level would block the node's own RFC1918.New-NetFirewallHyperVRuleby VMCreatorId (fix(networking): enforce Windows container egress at the Hyper-V firewall #140) — no container VMCreator exists on this host.The experiments (all on metal, branch
test/containment-suite)Single-IP experiments targeted Grafana (192.168.10.45) only — blocking it doesn't disconnect the runner, so "Grafana blocked, other three reachable" would have been the clean scoped success.
IPFORWARD_V4IPFORWARD_V4INBOUND_IPPACKET_V4IP_LOCAL_ADDRESS)=Grafana AND arrival if9Containment Windows egress log (matrix run, 7 filters installed)
All four reached — none of the seven filters caught the container. The load-bearing one is the post-NAT
OUTBOUND_IPPACKET_V4dest-only block: after SNAT the container's packet must leave on ifIndex 7 with dest 192.168.10.45, and it still wasn't stopped.Positive controls — WFP is enforced here
Same layer/field/value pointed at the host's own traffic:
The engine blocks traffic that reaches it — it blocked the host. It just never sees the container's SNAT'd packet.
Datapath facts that explain it
Get-NetNat— empty (the HNS NAT is not a NetNat object).Forwardingis Disabled on both the NAT vSwitch (if9) and the LAN NIC (if7).The NAT + forward happens inside the Hyper-V vSwitch / HNS datapath, out-of-band of the host
tcpip.sysWFP hooks.Conclusion / recommendation
Host-side software (WFP included) cannot enforce Windows container egress on this stack. Use network-level isolation — put the Windows runner on a VLAN / managed switch whose uplink denies the RFC1918 management ranges. The per-endpoint HNS ACLs stay as a best-effort layer for Windows builds where VFP does manage the ports, but on this build they are inert and must not be relied on.
Reproducer + full evidence:
docs/arch/windows-egress-wfp-investigation.md. Binding used:github.com/tailscale/wf(pure Go, no driver); intentionally not added togo.modsince there is no working mechanism to ship.