mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-08 14:36:13 +00:00
refactor(xhttp): split fields by direction, expand outbound coverage
Audit panel xhttp config against xray-core's runtime paths and split fields per direction so each side carries only what it actually uses: - Bidirectional (must match): host, path, mode, all xPadding*, session*/seq*, uplinkData*/Key, scMaxEachPostBytes - Server-only (inbound): noSSEHeader, scMaxBufferedPosts, scStreamUpServerSecs, serverMaxHeaderBytes - Client-only (outbound): uplinkHTTPMethod, uplinkChunkSize, noGRPCHeader, scMinPostsIntervalMs, xmux The inbound previously held client-only fields and the outbound was missing every must-match field beyond host/path/mode — meaning a panel-built outbound couldn't connect to an inbound with a custom xPaddingKey/sessionKey/etc. Headers stay on the inbound for URL-share purposes only; xray's listener ignores them at runtime, but they travel through the share link's `extra` blob so the client picks them up. Renames the URL helpers (applyXhttpPadding* -> applyXhttpExtra*) since the blob now carries more than padding, and folds path/host/mode into the helper so each link generator's xhttp branch is one line. Adds two enforcement points for xray's "uplinkHTTPMethod=GET only in packet-up" rule: the GET option is disabled when mode != packet-up, and a watcher on the outbound modal auto-clears GET when the user switches modes. Hides the XMUX block behind an `enableXmux` switch on the outbound form (mirrors the QUIC Params toggle) so the section doesn't clutter the form by default; fromJson auto-flips it on for outbounds with saved xmux config. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -582,28 +582,18 @@ func applyShareNetworkParams(stream map[string]any, streamNetwork string, params
|
||||
applyPathAndHostParams(httpupgrade, params)
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
applyPathAndHostParams(xhttp, params)
|
||||
params["mode"], _ = xhttp["mode"].(string)
|
||||
applyXhttpPaddingParams(xhttp, params)
|
||||
applyXhttpExtraParams(xhttp, params)
|
||||
}
|
||||
}
|
||||
|
||||
func applyXhttpPaddingObj(xhttp map[string]any, obj map[string]any) {
|
||||
// VMess base64 JSON supports arbitrary keys; copy the padding
|
||||
// settings through so clients can match the server's xhttp
|
||||
// xPaddingBytes range and, when the admin opted into obfs
|
||||
// mode, the custom key / header / placement / method.
|
||||
// applyXhttpExtraObj copies the bidirectional xhttp settings into the
|
||||
// VMess base64 JSON link object. VMess supports arbitrary keys, so we
|
||||
// flatten the SplitHTTPConfig "extra" fields directly onto obj.
|
||||
func applyXhttpExtraObj(xhttp map[string]any, obj map[string]any) {
|
||||
if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
|
||||
obj["x_padding_bytes"] = xpb
|
||||
}
|
||||
if obfs, ok := xhttp["xPaddingObfsMode"].(bool); ok && obfs {
|
||||
obj["xPaddingObfsMode"] = true
|
||||
for _, field := range []string{"xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"} {
|
||||
if v, ok := xhttp[field].(string); ok && len(v) > 0 {
|
||||
obj[field] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
maps.Copy(obj, buildXhttpExtra(xhttp))
|
||||
}
|
||||
|
||||
func applyVmessNetworkParams(stream map[string]any, network string, obj map[string]any) {
|
||||
@@ -639,8 +629,10 @@ func applyVmessNetworkParams(stream map[string]any, network string, obj map[stri
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
applyPathAndHostObj(xhttp, obj)
|
||||
obj["mode"], _ = xhttp["mode"].(string)
|
||||
applyXhttpPaddingObj(xhttp, obj)
|
||||
if mode, ok := xhttp["mode"].(string); ok {
|
||||
obj["mode"] = mode
|
||||
}
|
||||
applyXhttpExtraObj(xhttp, obj)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,45 +920,33 @@ func searchKey(data any, key string) (any, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// applyXhttpPaddingParams copies the xPadding* fields from an xhttpSettings
|
||||
// map into the URL query params of a vless:// / trojan:// / ss:// link.
|
||||
// buildXhttpExtra walks an xhttpSettings map and returns the JSON blob
|
||||
// that goes into the URL's `extra` param (or, for VMess, the link
|
||||
// object). Carries ONLY the bidirectional fields from xray-core's
|
||||
// SplitHTTPConfig — i.e. the ones the server enforces and the client
|
||||
// must match. Strictly one-sided fields are excluded:
|
||||
//
|
||||
// Before this helper existed, only path / host / mode were propagated,
|
||||
// so a server configured with a non-default xPaddingBytes (e.g. 80-600)
|
||||
// or with xPaddingObfsMode=true + custom xPaddingKey / xPaddingHeader
|
||||
// would silently diverge from the client: the client kept defaults,
|
||||
// hit the server, and was rejected by its padding validation
|
||||
// ("invalid padding" in the inbound log) — the client-visible symptom
|
||||
// was "xhttp doesn't connect" on OpenWRT / sing-box.
|
||||
// - server-only (noSSEHeader, scMaxBufferedPosts, scStreamUpServerSecs,
|
||||
// serverMaxHeaderBytes) — client wouldn't read them, so emitting
|
||||
// them just bloats the URL.
|
||||
// - client-only (headers, uplinkHTTPMethod, uplinkChunkSize,
|
||||
// noGRPCHeader, scMinPostsIntervalMs, xmux, downloadSettings) — the
|
||||
// inbound config doesn't have them; the client configures them
|
||||
// locally.
|
||||
//
|
||||
// Two encodings are written so every popular client can read at least one:
|
||||
//
|
||||
// - x_padding_bytes=<range> — flat param, understood by sing-box and its
|
||||
// derivatives (Podkop, OpenWRT sing-box, Karing, NekoBox, …).
|
||||
// - extra=<url-encoded-json> — full xhttp settings blob, which is how
|
||||
// xray-core clients (v2rayNG, Happ, Furious, Exclave, …) pick up the
|
||||
// obfs-mode key / header / placement / method.
|
||||
//
|
||||
// Anything that doesn't map to a non-empty value is skipped, so simple
|
||||
// inbounds (no custom padding) produce exactly the same URL as before.
|
||||
func applyXhttpPaddingParams(xhttp map[string]any, params map[string]string) {
|
||||
// Truthy-only guards keep default inbounds emitting the same compact URL
|
||||
// they did before this helper grew.
|
||||
func buildXhttpExtra(xhttp map[string]any) map[string]any {
|
||||
if xhttp == nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
|
||||
params["x_padding_bytes"] = xpb
|
||||
}
|
||||
|
||||
extra := map[string]any{}
|
||||
|
||||
if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
|
||||
extra["xPaddingBytes"] = xpb
|
||||
}
|
||||
if obfs, ok := xhttp["xPaddingObfsMode"].(bool); ok && obfs {
|
||||
extra["xPaddingObfsMode"] = true
|
||||
// The obfs-mode-only fields: only populate the ones the admin
|
||||
// actually set, so xray-core falls back to its own defaults for
|
||||
// the rest instead of seeing spurious empty strings.
|
||||
for _, field := range []string{"xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"} {
|
||||
if v, ok := xhttp[field].(string); ok && len(v) > 0 {
|
||||
extra[field] = v
|
||||
@@ -974,7 +954,74 @@ func applyXhttpPaddingParams(xhttp map[string]any, params map[string]string) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(extra) > 0 {
|
||||
stringFields := []string{
|
||||
"sessionPlacement", "sessionKey",
|
||||
"seqPlacement", "seqKey",
|
||||
"uplinkDataPlacement", "uplinkDataKey",
|
||||
"scMaxEachPostBytes",
|
||||
}
|
||||
for _, field := range stringFields {
|
||||
if v, ok := xhttp[field].(string); ok && len(v) > 0 {
|
||||
extra[field] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Headers — emitted as the {name: value} map upstream's struct
|
||||
// expects. The server runtime ignores this field, but the client
|
||||
// (consuming the share link) honors it. Drop any "host" entry —
|
||||
// host already wins as a top-level URL param.
|
||||
if rawHeaders, ok := xhttp["headers"].(map[string]any); ok && len(rawHeaders) > 0 {
|
||||
out := map[string]any{}
|
||||
for k, v := range rawHeaders {
|
||||
if strings.EqualFold(k, "host") {
|
||||
continue
|
||||
}
|
||||
out[k] = v
|
||||
}
|
||||
if len(out) > 0 {
|
||||
extra["headers"] = out
|
||||
}
|
||||
}
|
||||
|
||||
if len(extra) == 0 {
|
||||
return nil
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
// applyXhttpExtraParams emits the full xhttp config into the URL query
|
||||
// params of a vless:// / trojan:// / ss:// link. Sets path/host/mode at
|
||||
// top level (xray's Build() always lets these win over `extra`) and packs
|
||||
// everything else into a JSON `extra` param. Also writes the flat
|
||||
// `x_padding_bytes` param sing-box-family clients understand.
|
||||
//
|
||||
// Without this, the admin's custom xPaddingBytes / sessionKey / etc. never
|
||||
// reach the client and handshakes are silently rejected with
|
||||
// `invalid padding (...) length: 0` — the client-visible symptom is
|
||||
// "xhttp doesn't connect" on OpenWRT / sing-box.
|
||||
//
|
||||
// Two encodings are written so every popular client can read at least one:
|
||||
//
|
||||
// - x_padding_bytes=<range> — flat param, understood by sing-box and its
|
||||
// derivatives (Podkop, OpenWRT sing-box, Karing, NekoBox, …).
|
||||
// - extra=<url-encoded-json> — full xhttp settings blob, which is how
|
||||
// xray-core clients (v2rayNG, Happ, Furious, Exclave, …) pick up the
|
||||
// bidirectional fields beyond path/host/mode.
|
||||
func applyXhttpExtraParams(xhttp map[string]any, params map[string]string) {
|
||||
if xhttp == nil {
|
||||
return
|
||||
}
|
||||
applyPathAndHostParams(xhttp, params)
|
||||
if mode, ok := xhttp["mode"].(string); ok {
|
||||
params["mode"] = mode
|
||||
}
|
||||
|
||||
if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
|
||||
params["x_padding_bytes"] = xpb
|
||||
}
|
||||
|
||||
extra := buildXhttpExtra(xhttp)
|
||||
if extra != nil {
|
||||
if b, err := json.Marshal(extra); err == nil {
|
||||
params["extra"] = string(b)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user