{"record":{"id":"1d8c27a714b0a268","repo":"XTLS/Xray-core","slug":"the-cipher-is-not-support-single-port-multi-user","errorCode":null,"errorMessage":"The cipher is not support Single-port Multi-user","messagePattern":"The cipher is not support Single-port Multi-user","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/shadowsocks/validator.go","lineNumber":34,"sourceCode":"// Validator stores valid Shadowsocks users.\ntype Validator struct {\n\tsync.RWMutex\n\tusers []*protocol.MemoryUser\n\n\tbehaviorSeed  uint64\n\tbehaviorFused bool\n}\n\nvar ErrNotFound = errors.New(\"Not Found\")\n\n// Add a Shadowsocks user.\nfunc (v *Validator) Add(u *protocol.MemoryUser) error {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\taccount := u.Account.(*MemoryAccount)\n\tif !account.Cipher.IsAEAD() && len(v.users) > 0 {\n\t\treturn errors.New(\"The cipher is not support Single-port Multi-user\")\n\t}\n\tv.users = append(v.users, u)\n\n\tif !v.behaviorFused {\n\t\thashkdf := hmac.New(sha256.New, []byte(\"SSBSKDF\"))\n\t\thashkdf.Write(account.Key)\n\t\tv.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))\n\t}\n\n\treturn nil\n}\n\n// Del a Shadowsocks user with a non-empty Email.\nfunc (v *Validator) Del(email string) error {\n\tif email == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/proxy/shadowsocks/validator.go#L16-L52","documentation":"Policy error from Validator.Add: the account's cipher is not AEAD (IsAEAD() false, i.e. a legacy stream cipher) while the validator already holds at least one user. Single-port multi-user requires AEAD because only AEAD supports reliable per-user discrimination on a shared port; the check is skipped for the first user, so it only fires on the second-and-later non-AEAD add.","triggerScenarios":"Calling Validator.Add (directly or via inbound/API AddUser) with a legacy-cipher MemoryAccount when v.users is non-empty; order matters — two legacy users also fail (second one triggers), and legacy-after-AEAD fails.","commonSituations":"Migrating users onto a shared port incrementally; automation tools adding legacy-cipher users to an existing AEAD inbound; misunderstanding that a single legacy user alone on a port is allowed but mixed setups are not.","solutions":["Use AEAD ciphers exclusively on multi-user single-port inbounds.","Move legacy-cipher users to a dedicated inbound on a separate port.","Migrate the whole inbound to shadowsocks-2022 for first-class multi-user support."],"exampleFix":"// before\nvalidator.Add(legacyUser) // legacy cipher, users already present -> error\n// after\n// convert the account first\nlegacyUser.Account = &MemoryAccount{ CipherType: CipherType_CHACHA20_POLY1305 /* AEAD */, ... }","handlingStrategy":"validation","validationCode":"func canAddToValidator(v *Validator, acct *MemoryAccount) error {\n  if !acct.Cipher.IsAEAD() && len(v.users) > 0 {\n    return errors.New(\"non-AEAD cipher cannot join an occupied multi-user port\")\n  }\n  return nil\n}","typeGuard":"func isAEADAccount(a *MemoryAccount) bool { return a.Cipher.IsAEAD() }","tryCatchPattern":null,"preventionTips":["Check IsAEAD() on every account before Validator.Add.","Remember the rule is order-dependent: first user slips through, second fails — test the pair."],"tags":["shadowsocks","multi-user","aead","validation","users"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}