mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
fix(xray): heal shadowsocks per-client method across all start paths
xray-core's multi-user shadowsocks insists the per-client `method` matches the inbound's top-level cipher exactly for legacy ciphers, and is empty for 2022-blake3-*. The previous code (xray.go) copied `Client.Security` into the per-client `method` blindly, so a multi-protocol client created with the VMess default `"auto"` poisoned the SS config with `method: "auto"` → "unsupported cipher method: auto". Fix in two parts: - GetXrayConfig no longer projects `Client.Security` into the SS entry; the inbound's top-level method is now the single source of truth. - HealShadowsocksClientMethods moves to `database/model` and is invoked from `Inbound.GenXrayInboundConfig`, so the runtime add/update path (runtime.AddInbound) is normalised in addition to the full-restart path. For legacy ciphers heal now overwrites mismatched per-client methods rather than preserving them, so stale DB rows are also healed.
This commit is contained in:
parent
66f5026356
commit
ad8d58c2b6
3 changed files with 85 additions and 56 deletions
|
|
@ -219,17 +219,82 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
|||
}
|
||||
listen = fmt.Sprintf("\"%v\"", listen)
|
||||
protocol := string(i.Protocol)
|
||||
settings := i.Settings
|
||||
if i.Protocol == Shadowsocks {
|
||||
if healed, ok := HealShadowsocksClientMethods(settings); ok {
|
||||
settings = healed
|
||||
}
|
||||
}
|
||||
return &xray.InboundConfig{
|
||||
Listen: json_util.RawMessage(listen),
|
||||
Port: i.Port,
|
||||
Protocol: protocol,
|
||||
Settings: json_util.RawMessage(i.Settings),
|
||||
Settings: json_util.RawMessage(settings),
|
||||
StreamSettings: json_util.RawMessage(i.StreamSettings),
|
||||
Tag: i.Tag,
|
||||
Sniffing: json_util.RawMessage(i.Sniffing),
|
||||
}
|
||||
}
|
||||
|
||||
// HealShadowsocksClientMethods normalises the per-client `method` field
|
||||
// on a shadowsocks inbound's settings JSON before it leaves for xray-core:
|
||||
// - Legacy ciphers (aes-*, chacha20-*): every client must carry a
|
||||
// per-user `method` matching the inbound's top-level method, otherwise
|
||||
// xray fails with "unsupported cipher method:".
|
||||
// - Shadowsocks 2022 (2022-blake3-*): xray's multi-user code rejects the
|
||||
// inbound with "users must have empty method" when a client carries
|
||||
// one — strip stale entries left over from a switch off a legacy
|
||||
// cipher.
|
||||
// Returns the rewritten settings string and true when anything changed.
|
||||
func HealShadowsocksClientMethods(settings string) (string, bool) {
|
||||
if settings == "" {
|
||||
return settings, false
|
||||
}
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
|
||||
return settings, false
|
||||
}
|
||||
method, _ := parsed["method"].(string)
|
||||
clients, ok := parsed["clients"].([]any)
|
||||
if !ok {
|
||||
return settings, false
|
||||
}
|
||||
is2022 := strings.HasPrefix(method, "2022-blake3-")
|
||||
changed := false
|
||||
for i := range clients {
|
||||
cm, ok := clients[i].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if is2022 {
|
||||
if _, hasKey := cm["method"]; hasKey {
|
||||
delete(cm, "method")
|
||||
clients[i] = cm
|
||||
changed = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if method == "" {
|
||||
continue
|
||||
}
|
||||
existing, _ := cm["method"].(string)
|
||||
if existing == method {
|
||||
continue
|
||||
}
|
||||
cm["method"] = method
|
||||
clients[i] = cm
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
return settings, false
|
||||
}
|
||||
out, err := json.MarshalIndent(parsed, "", " ")
|
||||
if err != nil {
|
||||
return settings, false
|
||||
}
|
||||
return string(out), true
|
||||
}
|
||||
|
||||
// Setting stores key-value configuration settings for the 3x-ui panel.
|
||||
type Setting struct {
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
|
|
|
|||
|
|
@ -538,21 +538,31 @@ func shadowsocksKeyBytes(method string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
// applyShadowsocksClientMethod ensures each client entry carries a "method"
|
||||
// field for legacy shadowsocks ciphers. xray's multi-user shadowsocks code
|
||||
// requires a per-client method; an empty/missing field fails with
|
||||
// "unsupported cipher method:". 2022-blake3 ciphers use the top-level
|
||||
// method only, so the per-client field must stay absent.
|
||||
// applyShadowsocksClientMethod normalises the per-client "method" field
|
||||
// when an inbound is created or updated:
|
||||
// - Legacy ciphers: backfill `method` so xray's multi-user code is happy.
|
||||
// "unsupported cipher method:" otherwise.
|
||||
// - 2022-blake3-*: strip the per-client `method` because xray rejects
|
||||
// it with "users must have empty method". This matters after an admin
|
||||
// switches an existing inbound from a legacy cipher to a 2022 one.
|
||||
func applyShadowsocksClientMethod(clients []any, settings map[string]any) {
|
||||
method, _ := settings["method"].(string)
|
||||
if method == "" || strings.HasPrefix(method, "2022-blake3-") {
|
||||
return
|
||||
}
|
||||
is2022 := strings.HasPrefix(method, "2022-blake3-")
|
||||
for i := range clients {
|
||||
cm, ok := clients[i].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if is2022 {
|
||||
if _, hasKey := cm["method"]; hasKey {
|
||||
delete(cm, "method")
|
||||
clients[i] = cm
|
||||
}
|
||||
continue
|
||||
}
|
||||
if method == "" {
|
||||
continue
|
||||
}
|
||||
if existing, _ := cm["method"].(string); existing != "" {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,9 +180,6 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||
if c.Password != "" {
|
||||
entry["password"] = c.Password
|
||||
}
|
||||
if c.Security != "" {
|
||||
entry["method"] = c.Security
|
||||
}
|
||||
case model.Hysteria:
|
||||
if c.Auth != "" {
|
||||
entry["auth"] = c.Auth
|
||||
|
|
@ -246,7 +243,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||
}
|
||||
|
||||
if inbound.Protocol == model.Shadowsocks {
|
||||
if healed, ok := healShadowsocksClientMethods(inbound.Settings); ok {
|
||||
if healed, ok := model.HealShadowsocksClientMethods(inbound.Settings); ok {
|
||||
inbound.Settings = healed
|
||||
}
|
||||
}
|
||||
|
|
@ -307,49 +304,6 @@ func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage {
|
|||
return out
|
||||
}
|
||||
|
||||
// healShadowsocksClientMethods is the same idea as applyShadowsocksClientMethod
|
||||
// (see client.go) but applied at xray-config-build time, to backfill the
|
||||
// per-client method field for legacy shadowsocks inbounds whose clients were
|
||||
// stored before applyShadowsocksClientMethod existed. Returns the rewritten
|
||||
// settings string and true when anything actually changed.
|
||||
func healShadowsocksClientMethods(settings string) (string, bool) {
|
||||
if settings == "" {
|
||||
return settings, false
|
||||
}
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
|
||||
return settings, false
|
||||
}
|
||||
method, _ := parsed["method"].(string)
|
||||
if method == "" || strings.HasPrefix(method, "2022-blake3-") {
|
||||
return settings, false
|
||||
}
|
||||
clients, ok := parsed["clients"].([]any)
|
||||
if !ok {
|
||||
return settings, false
|
||||
}
|
||||
changed := false
|
||||
for i := range clients {
|
||||
cm, ok := clients[i].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if existing, _ := cm["method"].(string); existing != "" {
|
||||
continue
|
||||
}
|
||||
cm["method"] = method
|
||||
clients[i] = cm
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
return settings, false
|
||||
}
|
||||
out, err := json.MarshalIndent(parsed, "", " ")
|
||||
if err != nil {
|
||||
return settings, false
|
||||
}
|
||||
return string(out), true
|
||||
}
|
||||
|
||||
// GetXrayTraffic fetches the current traffic statistics from the running Xray process.
|
||||
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue