DEV Community

Cover image for Why My Windows Transparent Proxy Failed on Wi-Fi (and Worked on Ethernet)
Altug Tatlisu
Altug Tatlisu

Posted on

Why My Windows Transparent Proxy Failed on Wi-Fi (and Worked on Ethernet)

TL;DR:

My transparent proxy was architecturally correct, fully implemented, and thoroughly tested — yet it silently failed on Wi-Fi. The reason wasn’t a bug, a missing flag, or bad code. It was a Windows + Wi-Fi driver boundary that user-mode tools like WinDivert simply cannot cross.

This article documents the journey, the dead ends, the hard evidence, and the final, correct conclusion.


Background

I was building a transparent TCP proxy on Windows using:

  • Rust
  • WinDivert (user-mode packet capture/injection)
  • SOCKS5 upstream
  • Full DNAT / SNAT redirection
  • No explicit proxy configuration on the client

The goal was simple:

Intercept outbound TCP connections (ports 80/443), transparently redirect them to a local proxy, and forward traffic upstream — without breaking applications or TCP semantics.

This is a solved problem on Linux (iptables + REDIRECT).
On Windows, it’s much harder.


Phase 1: The Wrong Idea (Packet Forging)

The initial design attempted to:

  1. Intercept outbound SYN packets
  2. Drop them
  3. Forge a SYN-ACK pretending to be the real server
  4. Maintain a user-space TCP state machine
  5. Relay data via SOCKS5

On paper, everything worked:

  • Correct TCP flags
  • Correct SEQ/ACK numbers
  • Correct IP + TCP checksums
  • Verified hex dumps
  • Passing unit tests

Reality:
Windows silently dropped every forged SYN-ACK.

No ACK.
No handshake.
No error.

Root Cause

Windows enforces TCP anti-spoofing below WinDivert.

User-mode code cannot inject packets claiming to originate from external IPs and expect the TCP stack to accept them.

This is not configurable.
No WinDivert layer fixes this.
Not Impostor, not REFLECT, not FORWARD.

Conclusion:
Forging TCP peers in user mode on Windows is architecturally impossible.


Phase 2: The Correct Architecture (Redirection)

After discarding packet forging entirely, I implemented the only correct approach:

Transparent Redirection (DNAT / SNAT)

  1. Intercept outbound SYN
  2. Record original destination (dst_ip, dst_port)
  3. Rewrite destination → LOCAL_IP:8899
  4. Flip packet direction to INBOUND
  5. Let Windows complete a real TCP handshake
  6. Proxy connects upstream via SOCKS5
  7. Rewrite source on return packets back to original server
  8. Flip direction to OUTBOUND
  9. Clean up mappings on FIN/RST

Key rules:

  • No forged packets
  • No custom TCP state machine
  • Windows owns TCP semantics

This is how all real transparent proxies work on Windows.


Everything Was Correct — Yet Nothing Worked

Even with the correct architecture:

  • curl hung
  • No outbound SYN visible
  • WinDivert captured only inbound packets
  • Outbound=1 never appeared
  • NETWORK_FORWARD layer captured nothing
  • Filters were correct
  • Injection logic was correct

At this point, the usual suspects were gone.


The Critical Discovery: Wi-Fi Fast-Path

The system used an Intel Wi-Fi adapter.

After exhaustive testing:

  • WinDivert never saw outbound TCP packets
  • Even with filter "tcp"
  • Even after disabling all configurable offloads
  • Even after restarting the adapter
  • Even at different WinDivert layers

PowerShell confirmed it:

Get-NetAdapterAdvancedProperty -Name "Wi-Fi"
Enter fullscreen mode Exit fullscreen mode

Only WoWLAN offloads existed.
No TCP offload toggles.
No segmentation controls.

What This Means

On this Wi-Fi adapter:

  • Outbound TCP packets are constructed below NDIS
  • They bypass filter drivers entirely
  • They never appear as discrete IP packets
  • User-mode tools cannot intercept them

Inbound packets still appear — because they must traverse the receive path.

This is a driver + firmware fast-path, not a bug.


Why Ethernet Works (and Wi-Fi Doesn’t)

Ethernet drivers typically:

  • Do not use hard fast-paths
  • Expose outbound packets through NDIS
  • Allow WinDivert to see and modify traffic

Wi-Fi drivers often:

  • Offload TCP construction to firmware
  • Skip NDIS filter layers
  • Make outbound interception impossible in user mode

This is why:

  • Commercial VPNs use kernel-mode WFP drivers
  • User-mode tools work reliably on Ethernet
  • Wi-Fi support requires deeper hooks

Final Resolution

I chose the most pragmatic and correct next step:

USB-to-Ethernet Adapter

  • ~$15
  • Zero code changes
  • Immediate validation

This isn’t a workaround — it’s crossing a known platform boundary.

On Ethernet:

  • Outbound packets appear
  • DNAT/SNAT works
  • TCP handshake completes
  • Proxy exit IP is visible
  • Architecture is fully validated

Key Takeaways

1. You cannot forge TCP peers on Windows in user mode

Anti-spoofing is enforced below WinDivert.

2. Transparent proxies must use redirection, not forgery

Let the OS own TCP.

3. WinDivert is correct — but limited by NIC drivers

Especially on Wi-Fi.

4. Absence of packets can be a hardware truth, not a bug

Empirical testing matters more than assumptions.

5. Ethernet validates architecture; Wi-Fi dictates tooling

User mode vs kernel mode is not a preference — it’s a requirement.


When Do You Need WFP?

Only if:

  • Transparent interception over Wi-Fi is mandatory
  • Production deployment is required
  • Kernel development is acceptable

Otherwise:

  • User-mode + Ethernet is sufficient
  • Architecture is sound
  • Complexity stays manageable

Closing

This project did not fail.

It reached a correct architectural conclusion, backed by:

  • Clean code
  • Empirical testing
  • Layer isolation
  • Hardware-level evidence

Sometimes the hardest bugs aren’t bugs at all — they’re boundaries.

If this saves you weeks of chasing ghosts, it did its job.

Top comments (0)