Skip to content

Hysteria server: Fix Unix masquerade socket path - #6705

Merged
RPRX merged 1 commit into
XTLS:mainfrom
XXcipherX:fix/hysteria-unix-masquerade-path
Sep 7, 2026
Merged

Hysteria server: Fix Unix masquerade socket path#6705
RPRX merged 1 commit into
XTLS:mainfrom
XXcipherX:fix/hysteria-unix-masquerade-path

Conversation

@XXcipherX

@XXcipherX XXcipherX commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem

The current Xray code handles Unix-socket targets in the following order:

case "", "unix":
    u = &url.URL{Scheme: "http", Host: "localhost"}
    path := u.Path

    dialer := &net.Dialer{Timeout: 30 * time.Second}
    transport = transport.Clone()
    transport.Proxy = nil
    transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
        return dialer.DialContext(ctx, "unix", path)
    }

The original parsed URL is overwritten before its path is saved:

u = &url.URL{Scheme: "http", Host: "localhost"}

The replacement URL has an empty Path, so the next assignment always produces:

path == ""

Consequently, the custom transport effectively attempts:

dialer.DialContext(ctx, "unix", "")

On Linux, this fails with an error similar to:

dial unix: missing address

The Hysteria masquerade handler then returns 502 Bad Gateway, while Xray logs:

HTTP reverse proxy error

This affects all supported Unix-socket target formats, not just one particular URL form, because the parsed socket path is discarded in every case.

Upstream behavior

In the upstream Hysteria implementation, the socket path is saved before constructing the synthetic HTTP target:

socketPath := parsedURL.Path

Only after that does it return the target used by httputil.ReverseProxy:

return &url.URL{
    Scheme: "http",
    Host:   "localhost",
}, transport, nil

The custom transport therefore captures the original socket path:

transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
    return dialer.DialContext(ctx, "unix", socketPath)
}

The incorrect ordering was introduced while adapting this logic in #6565 it is not caused by the upstream Hysteria implementation.

Fix

Save u.Path before replacing u with the synthetic http://localhost target:

case "", "unix":
    path := u.Path
    u = &url.URL{Scheme: "http", Host: "localhost"}

This preserves the original Unix socket address while leaving HTTP and HTTPS masquerade behavior unchanged.

@LjhAUMEM

LjhAUMEM commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

这里确实同步错了,感谢修复

改的代码行数少的话也不用写这么多描述,看代码就可以知道意图

@RPRX RPRX changed the title Hysteria: Fix Unix masquerade socket path Hysteria server: Fix Unix masquerade socket path Sep 7, 2026
@RPRX
RPRX merged commit de2caf3 into XTLS:main Sep 7, 2026
40 checks passed
TaiLerV pushed a commit to TaiLerV/Xray-core-fix that referenced this pull request Sep 10, 2026
Upstream changes of note:
- Finalmask: "udpHop" became a UDP mask of its own (XTLS#6327). UdpHop left
  transport.internet.QuicParams and transport/internet/hysteria/udphop moved
  to transport/internet/finalmask/udphop. QuicParams field numbers shifted.
- Direct/Freedom: transport.internet.ProxyConfig was removed; dialerProxy is
  reached through sockopt (XTLS#6058, XTLS#6742).
- XHTTP client: upstream fixed the WaitReadCloser data race (XTLS#6694) that this
  fork had already fixed independently.
- Hysteria server: upstream fixed the Unix masquerade socket path (XTLS#6705),
  the same bug this fork fixed in merge commit 5034aab.

Conflict resolutions:
- core/core.go, infra/conf/transport_security.go: fork behavior kept. REALITY
  keeps no built-in minClientVer default; the version stamp is left at
  26.9.5-0936 because stamping is a separate release step.
- transport/internet/config.proto: upstream layout adopted, dropping the
  fork's udp_hop=5 numbering. Binary protobuf configs written by earlier fork
  builds must be regenerated; JSON configs are unaffected.
- transport/internet/config_compat_test.go: repinned to upstream's field
  numbers and re-anchored to a wire blob for the current schema. The previous
  blob encoded the fork layout and cannot survive the renumbering.
- proxy/freedom/freedom.go: upstream's resolution rewrite taken; the fork's
  matchIP fast path, lazy connection-opened log, and TryCloseWrite handling
  kept. logFreedomDialDestination and its benchmarks are gone with their call
  site.
- transport/internet/splithttp/client.go: upstream's done.Instance
  implementation taken over the fork's mutex version; the fork's regression
  test now drives that API.
- transport/internet/hysteria/hub.go: fork's empty-unix-path guard kept on top
  of upstream's reorder.
- proxy/blackhole/blackhole_test.go: upstream's 403 and custom-response
  assertions kept, both tests moved onto the fork's race-free channel pattern.
- transport/internet/finalmask/xdns/config.go: dead UDP() marker dropped with
  upstream's Udpmask interface; fork's level warnings kept.
- Generated protobuf regenerated with protoc-gen-go v1.36.11 / protoc v6.33.5.

Validation: go build, go vet, and vformat check are clean. go test ./...
matches origin/main exactly. Five failures are reported by the full run
(TestChinaSites, TestCompactDomainMatcher_PreservesMixedRuleIndices,
TestIPMatcher4CN, TestGeodataConfig, TestWireguard); TestGeodataAssetConfig is
a sixth, reached only once TestGeodataConfig is skipped, because its panic
aborts the package first. All six reproduce identically on origin/main and are
caused by the absent /resources/ geodata assets and by external network use.
Race gate green on reality, proxy, vless, singmux, mux, splithttp, blackhole.
checkptr=2 green. Linux/amd64 CGO_ENABLED=0 GOAMD64=v1 -trimpath build
produces a working static binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGewp7yHhqerRXzd1D9eev
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.

3 participants