#3197·mihomo

[Bug] h2mux multiplex: half-close truncates uploads (RST_STREAM instead of END_STREAM)

Author: proxi9Created Sep 10, 2026Updated Sep 15, 2026
Labelsbug

Update 2026-09-15: do not bump sing-mux to v0.3.11. sing-mux#2 fixed h2mux, but it regressed yamux: CloseWrite maps to Stream.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

yaml
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,node

Description

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

bash
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.