Skip to content

[Linux] NFQUEUE interception stalls heavy loopback (127.0.0.0/8) transfers #164

Description

@rsyuzyov

Describe the bug

On Linux, while ProxyBridge is active (NFQUEUE on mangle/OUTPUT), HTTP requests to
local services on 127.0.0.1 that return a non-trivial amount of data hang (time out,
0 bytes received). Small/quick responses to the same ports return instantly, and external
traffic is unaffected.

This reproduces even with no rules and no proxy configured (pure pass-through), so the
cause is the NFQUEUE interception itself, not any PROXY action. Only ProxyBridge --cleanup
or stopping the process restores loopback — disabling rules in the config/GUI does not.

Environment

  • OS: Debian 13 (trixie)
  • ProxyBridge: dev (V4.0)
  • Platform: Linux

To reproduce

# 1. local service with a heavy response
dd if=/dev/urandom of=/srv/big.bin bs=1M count=16
( cd /srv && python3 -m http.server 8080 --bind 127.0.0.1 & )

# 2. start ProxyBridge with NO rules and NO proxy
sudo ProxyBridge --verbose 1

# 3. heavy loopback request -> HANGS
curl --max-time 12 http://127.0.0.1:8080/big.bin     # timeout, 0 bytes

# 4. small request + external -> fine
curl http://127.0.0.1:8080/                          # 200 instantly
curl https://ifconfig.me                             # 200

# 5. remove interception -> heavy request works again
sudo ProxyBridge --cleanup
curl http://127.0.0.1:8080/big.bin                   # 200 instantly

Root cause (confirmed)

ProxyBridge sends all OUTPUT packets — including loopback — to NFQUEUE and reinjects them.
The lo interface has an MTU of 65536, so loopback TCP uses very large segments (~64 KB),
and these do not survive the NFQUEUE -> userspace -> reinject path. Small responses fit in
the initial TCP window and complete before this matters.

Confirmed by A/B (interception active the whole time):

lo MTU curl big.bin (16 MB over loopback)
65536 (default) timeout, 0 bytes
1500 200, ~0.05 s
65536 (restored) timeout, 0 bytes again

Toggling only the MTU toggles the bug, which pins the cause to loopback segment size rather
than queue size, rule matching, or DNS. (copy_range is not involved: DIRECT issues an
ACCEPT verdict by packet id, and the PROXY path moves data through the relay sockets, not
the queue.)

Proposed fix

Exclude loopback from interception by inserting

iptables -t mangle -A OUTPUT -o lo -j ACCEPT

before the NFQUEUE rules (and removing it symmetrically on cleanup). Loopback never needs
proxying in the default configuration. This also matches the Windows behaviour, where
localhost traffic is DIRECT by default unless LocalhostViaProxy is enabled.

I have a fix + tests ready and can open a PR against dev.

Future enhancement (separate PR, optional)

The Linux build currently has no LocalhostViaProxy toggle, while Windows does. A follow-up
could add it for parity. Making LocalhostViaProxy = true actually proxy loopback is feasible
without globally changing the lo MTU, by having ProxyBridge clamp the TCP MSS only on the
intercepted loopback path (e.g. TCPMSS --set-mss on lo SYN packets), so segments stay small
enough to survive reinjection. Happy to do this as a second PR if it is of interest.


Note: this was investigated and fixed with Claude Code (Opus 4.8); I am not a C developer
myself. I have reproduced and verified the fix on Debian 13, but I am equally happy for you to
take it from here and implement it yourselves if you prefer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions