DanielLavrushin 记录 b4 上游 UDP 转发与阻止 QUIC 的耦合问题
DanielLavrushin 在 b4 #456 中记录:代理集合把 UDP 送往上游时,“Block QUIC”对已被 TPROXY 接管的流量不起作用;关闭上游 UDP 又会让匹配的 UDP 直出,用户报告因此暴露公网地址。作者提出让阻止 QUIC 对这类集合生效:匹配的 UDP 443 避开 TPROXY 并进入集合的拒绝链,其他匹配 UDP 仍走上游,未匹配的 UDP 443 不受影响,并计划覆盖 nftables、iptables、IPv4/IPv6 和路由器自身流量。该项仍是方案,尚未修复;部分 Asuswrt-Merlin 的既有 FORWARD 接受规则还会使拒绝链无法触发。
作者原文@DanielLavrushinA proxy set (Routing Mode Upstream SOCKS5 proxy) cannot refuse QUIC while it routes the rest of its UDP through the upstream. Route UDP through upstream decides both at once, and the Block QUIC (force TCP) switch on the UDP tab, which the set editor offers on proxy sets too, has no effect once UDP goes upstream.
Reported by a user running b4 1.85 as a transparent gateway in a MikroTik container, with a proxy set whose upstream (3proxy) leaves through a VPN tunnel that only has TCP transport. Still the same on
main.What the user sees
- Route UDP through upstream off: matched UDP leaves directly over the WAN. A WebRTC STUN request (UDP to port 3478) then reveals the ISP's public address; IPLeak's WebRTC section shows it next to the device's local address. Dropping that one UDP flow on the router makes the WAN address disappear from the test.
- Route UDP through upstream on: the WebRTC leak is gone, but matched QUIC (UDP 443), YouTube on Android included, now goes through SOCKS5 UDP ASSOCIATE and the TCP-only tunnel. Video buffers for a long time and loads slowly.
- Rejecting UDP 443 on the router fixes YouTube by forcing TCP, but refuses QUIC to every address, including ones no set matches. b4 is used for selective routing, so that is not an option.
The combination the user needs does not exist: matched TCP to the upstream, matched UDP 443 refused so clients fall back to TCP, other matched UDP (STUN included) through the upstream, unmatched UDP 443 untouched.
Why
The two settings are coupled in one line,
src/tables/routing.go:626:st.quicReject = mode == config.RoutingModeProxy && !set.Routing.Upstream.UDPFlipping that condition alone would not help, because of where the rules sit:
- With UDP upstream on, the set's UDP TPROXY rule is in the
b4_routeprerouting chain and matches all UDP to the set's addresses (addProxyTProxyRuleNft/addProxyTProxyRuleIpt,src/tables/routing_proxy.go:393-424). A matched UDP 443 packet is diverted to b4's local listener before the routing decision.- The set's QUIC reject chain is reached from forward and output (
routeEnsureQUICReject,src/tables/routing_proxy.go:459-504), so a TPROXY'd packet never gets there.- The UDP tab's Block QUIC (force TCP) sets UDP Action Mode to Reject with QUIC Matching on matched destinations (
src/http/ui/src/components/sets/Udp.tsx:134-145). That acts in the packet queue, which client UDP reaches only at forward and postrouting, so it never sees a TPROXY'd packet either. The switch is hidden only for sets that handle calls only, so a proxy set offers it and it silently does nothing.Proposed change
No new setting: make Block QUIC (force TCP) work on a proxy set that routes UDP through its upstream.
- When such a set blocks QUIC, leave
udp dport 443out of its UDP TPROXY rule (nftables and iptables, IPv4 and IPv6), and out of the router-output mark rule for the router's own traffic, so UDP 443 continues past the routing decision.- Build the set's existing QUIC reject chain in that case too:
quicRejectbecomes true for a proxy set that has UDP upstream off or blocks QUIC. The reject rule already matches only the set's address sets, so UDP 443 to other addresses passes.- Other matched UDP, STUN included, keeps going through the upstream.
Tests: the routing netns suite (
make test-netns) for both backends, covering a matched UDP 443 refusal with ICMP port unreachable, matched UDP to another port reaching the TPROXY listener, unmatched UDP 443 passing, and router-originated traffic. Docs: the UDP and routing pages, EN and RU.Caveat
On iptables the QUIC reject chain uses the same appended FORWARD jump as block mode, which was measured to never fire on Asuswrt-Merlin because of an earlier
-A FORWARD -i br0 -j ACCEPT. The reporter's setup (b4 in its own container netns) is not affected, but the change inherits that limit wherever it applies.