mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 21:24:10 +00:00
Fix: propagate xhttp xPadding settings into generated subscription links
The four `genXLink` helpers in `sub/subService.go` only copied `path`,
`host` and `mode` out of `xhttpSettings` when building vmess:// /
vless:// / trojan:// / ss:// URLs. Everything else — `xPaddingBytes`,
`xPaddingObfsMode`, `xPaddingKey`, `xPaddingHeader`,
`xPaddingPlacement`, `xPaddingMethod` — was silently dropped.
That meant an admin who set, say, `xPaddingBytes: "80-600"` plus obfs
mode with a custom `xPaddingKey` on the inbound had a server config
that no client could match from the copy-pasted link: the client kept
the xray/sing-box internal defaults (`100-1000`, `x_padding`,
`Referer`), hit the server, and was rejected by
invalid padding (queryInHeader=Referer, key=x_padding) length: 0
The user-visible symptom on OpenWRT / Podkop / sing-box was
"xhttp inbound just won't connect" — no obvious pointer to what was
actually wrong because the link itself *looks* complete.
Fix:
* New helper `applyXhttpPaddingParams(xhttp, params)` writes
`x_padding_bytes=<range>` (flat, sing-box family reads this) and
an `extra=<url-encoded-json>` blob carrying the full set of xhttp
settings (xray-core family reads this). Both encodings are emitted
side-by-side so every mainstream client can pick at least one up.
* All four link generators (`genVmessLink` via the obj map,
`genVlessLink`, `genTrojanLink`, `genShadowsocksLink`) now invoke
the copy.
* Obfs-only fields (`xPaddingKey`, `xPaddingHeader`,
`xPaddingPlacement`, `xPaddingMethod`) are only included when
`xPaddingObfsMode` is actually true and the admin filled them in.
An inbound with no custom padding produces exactly the same URL
as before — existing subscriptions are unaffected.
This commit is contained in:
parent
faec3ca038
commit
37ac0a2abd
1 changed files with 71 additions and 0 deletions
|
|
@ -250,6 +250,21 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
obj["mode"], _ = xhttp["mode"].(string)
|
obj["mode"], _ = xhttp["mode"].(string)
|
||||||
|
// 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.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
|
|
@ -408,6 +423,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
params["mode"], _ = xhttp["mode"].(string)
|
params["mode"], _ = xhttp["mode"].(string)
|
||||||
|
applyXhttpPaddingParams(xhttp, params)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
|
|
@ -604,6 +620,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
params["mode"], _ = xhttp["mode"].(string)
|
params["mode"], _ = xhttp["mode"].(string)
|
||||||
|
applyXhttpPaddingParams(xhttp, params)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
|
|
@ -803,6 +820,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
params["mode"], _ = xhttp["mode"].(string)
|
params["mode"], _ = xhttp["mode"].(string)
|
||||||
|
applyXhttpPaddingParams(xhttp, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
|
@ -1057,6 +1075,59 @@ func searchKey(data any, key string) (any, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyXhttpPaddingParams copies the xPadding* fields from an xhttpSettings
|
||||||
|
// map into the URL query params of a vless:// / trojan:// / ss:// link.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
if xhttp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(extra) > 0 {
|
||||||
|
if b, err := json.Marshal(extra); err == nil {
|
||||||
|
params["extra"] = string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func searchHost(headers any) string {
|
func searchHost(headers any) string {
|
||||||
data, _ := headers.(map[string]any)
|
data, _ := headers.(map[string]any)
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue