Skip to content

luci-app-ssr-plus: fix foreign QUIC direct connection and mihomo tproxy port. - #2052

Open
zxlhhyccc wants to merge 14 commits into
fw876:devfrom
zxlhhyccc:rules
Open

zxlhhyccc wants to merge 14 commits into
fw876:devfrom
zxlhhyccc:rules

Conversation

@zxlhhyccc

Copy link
Copy Markdown
Collaborator

No description provided.

@vsmirn0v

Copy link
Copy Markdown

@zxlhhyccc Thanks for publishing this. I reviewed the QUIC commit 7737bc3. The Mihomo port offset looks consistent, but I see two routing cases that the build checks do not cover:

  1. The proposed UDP-capability group / REJECT fallback is not in this commit: clash_yaml.lua changes only tproxy-port. For an imported profile with DOMAIN-SUFFIX,foreign.example,PROXY followed by MATCH,DIRECT, switching PROXY to a member with udp: false makes Mihomo skip the first rule and use DIRECT. The new firewall TPROXY rule sends UDP/443 into that unchanged rule list, so the foreign direct fall-through remains. We reproduced this selector-switch behavior with live Mihomo in luci-app-ssr-plus: align UDP/443 routing with node settings #2051. Could you add a test for this case and the intended fallback?
  2. For a TCP-only node such as NaiveProxy, get_udp_relay_mode returns disabled, Start_Run passes -y (no -u/-U), and tp_rule returns without installing a UDP interception path. The NAT UDP/443 drop is also skipped when DISABLE_UDP_RULES=1. Thus proxy-selected UDP/443 is not rejected or proxied on that path. Is direct egress intended there? If not, it needs a selected-traffic reject path.

Please also test fake-IP UDP with no TPROXY and router-originated UDP after removing the UDP NAT redirects and adding the OUTPUT mark; those paths changed but have no packet-level regression tests in this PR. I have not applied this PR to the production router.

@zxlhhyccc

Copy link
Copy Markdown
Collaborator Author

@vsmirn0v I think passing the -y parameter to Start_Run (without -u/-U) is only intended for a single node, and does not use a multi-node YAML file. Also, I don't have a node that supports UDP/443, so I can't test it for now.

@vsmirn0v

Copy link
Copy Markdown

@zxlhhyccc Thanks. Yes, -y is the single-node TCP-only path; I was not suggesting it is used for imported multi-node YAML. The two cases are separate:

  • Single TCP-only node (for example NaiveProxy): -y installs no UDP TPROXY listener and suppresses the existing UDP/443 DROP. What prevents a proxy-selected UDP/443 packet from leaving directly in that mode?
  • Imported Mihomo YAML: this uses the native UDP path, not -y. In 7737bc3, clash_yaml.lua only changes the TPROXY port and does not add the proposed UDP-capability group/fallback. With a TCP-only selected member, Mihomo can still skip a proxy rule and reach a later DIRECT rule.

A real UDP-capable server is not needed to test the Mihomo routing decision. #2051 has a synthetic profile with dummy udp: true/udp: false SOCKS5 members and a network-namespace test that checks Mihomo's routing logs after switching the selector. Could you run an equivalent case against #2052? I am happy to compare the results.

@zxlhhyccc

zxlhhyccc commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

@vsmirn0v
After adding, for example, the rule under iptables that marks the router's own foreign QUIC and sends it into the TProxy chain:

$ipt -A OUTPUT -p udp --dport 443 -m set ! --match-set china dst -m comment --comment "$TAG" -j MARK --set-mark ${FWMARK}

changing the original rule:

$ipt -A SS_SPEC_TPROXY -p udp --dport 443 -j DROP

to:

$ipt -A SS_SPEC_TPROXY -p udp --dport 443 -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} # enable foreign QUIC

and deleting rules such as:

$IPT -I PREROUTING 2 ${IFNAME:+-i $IFNAME} -p udp -d 198.18.0.0/16 -j REDIRECT --to-ports "$local_port" -m comment --comment "$TAG"

then, as long as the node supports UDP, UDP/443 will work normally in all cases. If the node does not have UDP enabled, the foreign server's QUIC will be forced through the TCP proxy.

Please test: zxlhhyccc@a2bfe58

image

@vsmirn0v

Copy link
Copy Markdown

@zxlhhyccc I tested the requested a2bfe588655e161bf1ee42d06dd61b64ebdefad5 (separate from the current #2052 head) in isolated Linux network namespaces, using its actual ac_rule*/tp_rule* functions. Results were the same with iptables-nft 1.8.10 and native nftables 1.0.9.

Ordinary LAN proxy-selected UDP/443 reaches the transparent UDP listener; China and explicit LAN bypass cases remain direct. However, I reproduced these gaps:

  1. The new OUTPUT mark breaks router-originated upstream/bypass traffic. With the configured upstream SERVER outside the China set and listening on UDP/443 in an isolated WAN namespace, the packet times out and the WAN receiver gets nothing. An explicit whitelist destination behaves the same. Removing only the newly added OUTPUT mark restores direct delivery in both cases. Also, with router proxying disabled (OUTPUT unset), ordinary foreign UDP/443 still reaches TPROXY; removing that mark restores the configured direct path. Please scope OUTPUT interception to the router-proxy setting and exempt upstream/bypass destinations and proxy sockets before setting the local-routing mark.
  2. Fake-IP interception is lost when the destination port is outside PROXY_PORTS. Set fake-IP on, permit only port 8443, and send to 198.18.0.10:443: the packet takes the direct forwarding path. The old unconditional fake-IP UDP NAT path was removed, but entry into the replacement TPROXY chain is still port-filtered. A fake-IP-specific entry preserving interface/LAN scope is needed.
  3. The independent TCP-only single-node case still goes direct. With TPROXY unset and DISABLE_UDP_RULES=1 (-y), selected UDP/443 is neither intercepted nor rejected.

I also ran Mihomo v1.19.31 with a synthetic udp: true/udp: false selector and a proxy rule followed by MATCH,DIRECT. After switching to the incapable member, the core logs [UDP] ... foreign.example:443 match Match using DIRECT. This is the core decision; the new OUTPUT mark can subsequently recapture that traffic, so it is not proof that every complete deployment successfully sends it out the WAN. It also does not route it through a TCP proxy. A browser may retry using TCP after UDP fails, but that is client fallback and these raw UDP tests do not measure browser fallback latency.

For reproducible LAN cases, use the packet harness from #2051, setting SSR_RULES to the supplied commit's ssr-rules:

sudo env SSR_RULES=/path/to/a2bfe58/ssr-rules WAN_BP_IP= WAN_FW_IP= \
  unshare --net bash test_udp443_policy.sh --inside iptables proxy router fake-ip-excluded-port
# FAIL: expected proxy, got direct
sudo env SSR_RULES=/path/to/a2bfe58/ssr-rules WAN_BP_IP= WAN_FW_IP= \
  unshare --net bash test_udp443_policy.sh --inside iptables reject router no-relay-disabled
# FAIL: expected reject, got direct

The router tests add a WAN namespace/UDP echo receiver and use the source functions unchanged; each removal control deletes only the new OUTPUT marking rule. For native nft tests, functions were invoked in conditional lists as in production; the inherited malformed DNS-return expression otherwise stops a set -e harness before packet testing. No production router was changed.

@zxlhhyccc

Copy link
Copy Markdown
Collaborator Author

@vsmirn0v Could we discuss this privately one-on-one? Posting here is quite slow, and the meaning can easily get lost in translation.

@vsmirn0v

Copy link
Copy Markdown

@zxlhhyccc Thanks—I understand the concern about translation and the pace of the discussion. Which private channel would you prefer? Please suggest a contact method you are comfortable sharing here. Once a channel is agreed, we can keep a short summary of the technical conclusions and test results in the PR for other reviewers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants