[Bug] h2mux multiplex: half-close truncates uploads (RST_STREAM instead of END_STREAM)
Update 2026-09-15: do not bump sing-mux to v0.3.11. sing-mux#2 fixed h2mux, but it regressed yamux:
CloseWritemaps toStream.Close(), which is a full close in metacubex/yamux, so yamux clients lose the response after half-close (on v0.3.10 yamux worked; only h2mux was affected). Fix + regression test: MetaCubeX/sing-mux#4. The mihomo bump should target the tag that includes #4 — I'll send that PR once it's released.
Verify steps
- I have read the documentation and understand the meaning of all the configuration items I have written, rather than just piling up seemingly useful options or default values.
- I have carefully reviewed the documentation and have not resolved the issue.
- I have searched the Issue Tracker for the issue I want to raise and did not find it.
- I am a non-Chinese user.
- I have tested with the latest Alpha branch version, and the issue still persists.
- I have provided the server and client configuration files and processes that can reproduce the issue locally, rather than a sanitized complex client configuration file.
- I provided the simplest configuration that can be used to reproduce the errors in my report, rather than relying on remote servers or piling on a lot of unnecessary configurations for reproduction.
- I have provided complete logs, rather than just the parts I think are useful out of confidence in my own intelligence.
- I have directly reproduced the error using the Mihomo command-line program, rather than using other tools or scripts.
Operating System
Linux
System Version
Linux (OS-independent bug); tested on CachyOS, kernel 7.2.3
Mihomo Version
Alpha (built from Alpha @a4899a3, 2026-09-10); code path unchanged since v1.19.30
Configuration File
proxies:
- name: node
type: vless
server: 192.0.2.1
port: 443
uuid: 00000000-0000-0000-0000-000000000000
network: tcp
tls: true
smux:
enabled: true
protocol: h2mux
padding: true
min-streams: 4
max-connections: 2
rules:
- MATCH,nodeDescription
Two related issues in the h2mux carrier of github.com/metacubex/sing-mux
(v0.3.10), which mihomo depends on directly. Both come with ready, built and
reproduced fixes as PRs against metacubex/sing-mux.
1. h2mux/yamux half-close is not propagated — uploads are aborted (present today).
A half-closed stream (write payload, CloseWrite(), then read the reply) has no
CloseWrite() on wrapStream/clientConn, so N.CloseWrite() falls back to a
full Close(). On h2mux that cancels the request context → RST_STREAM(CANCEL)
instead of END_STREAM, aborting in-flight upload bytes and dropping the
response. Fix (port of upstream SagerNet/sing-mux#8): MetaCubeX/sing-mux#2.
2. h2mux CONNECT request has a nil Header — breaks on golang.org/x/net ≥ v0.57.0 (latent).
http2.Transport.RoundTrip rejects a nil header map with http: nil Request.Header
since x/net v0.57.0, so no h2mux stream opens. mihomo pins x/net v0.35.0 today, so
this trips the next time x/net is bumped, not now. One-line fix (matching upstream
SagerNet/sing-mux@e57b352): MetaCubeX/sing-mux#3.
Both PRs build cleanly (go build/go vet/gofmt) and are straight ports of the
upstream SagerNet fixes. Please review/merge in sing-mux and bump the dependency in
mihomo. Happy to adjust anything.
Reproduction Steps
Isolated network namespace. Client = mihomo built from the Alpha branch against
the patched sing-mux (go.mod replace); server = an Xray-core fork that accepts
sing-mux carriers. Client sends N bytes, shutdown(SHUT_WR), waits for the echo
server's GOT <N> acknowledgement.
| mihomo build | protocol | payload | result |
|---|---|---|---|
| patched (sing-mux#2) | h2mux | 16 B | GOT 16 |
| patched (sing-mux#2) | h2mux | 1 MiB | GOT 1048576 |
| stock v0.3.10 | h2mux | 16 B | closed, no response |
| stock v0.3.10 | h2mux | 1 MiB | closed, no response (upload truncated mid-stream) |
For issue #2: an isolated http2.Transport.NewClientConn + RoundTrip of a
CONNECT request over a raw TCP pair (exactly as Open() builds it) returns
http: nil Request.Header under x/net v0.57.0/v0.58.0 with the current nil
header, and succeeds once Header: make(http.Header) is set.
Logs
No error is logged: sing-mux does not log carrier/stream lifecycle, so the
truncated upload and missing response are silent even at DEBUG level. The bug is
therefore reproduced at the protocol level (see Reproduction Steps) rather than
via mihomo logs.Source: MetaCubeX/mihomo