mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
cfa8350d10
* fix(clients): keep a vless reverse client's handler across a re-add RemoveUser also drops the client's reverse outbound handler, and the account every live remove/re-add path rebuilt carried no reverse at all: buildUserAccount read id/flow/testseed/testpre and nothing else. Editing, bulk re-enabling, quota renewal and adding a client to an existing inbound therefore left a reverse client able to connect but not to open its tunnel until Xray restarted, with nothing logged. A traffic reset is the route operators hit most, since a depleted client is removed and re-added on every renewal. buildUserAccount now carries the tag (it accepts either the settings JSON object or a typed client value), and the five account maps those paths build include the client's reverse. Core chain, read from the pinned xray-core: AddUserOperation -> User.ToMemoryUser -> vless.Account.AsAccount copies Reverse (proxy/vless/account.go:24), and GetReverse rebuilds the handler from the stored account's tag (proxy/vless/inbound/inbound.go:193-205). Each path has a test that fails without its fix; the account-level test fails on both input shapes. * refactor(clients): drop an account map helper nothing calls Local.AddClient and Local.UpdateUser are only reachable through runtime.Runtime, and all four call sites of those two methods sit in a node branch, where the runtime is a *Remote -- Remote.AddUser ignores the map and pushes the inbound snapshot instead. So the extraction and its test covered a path no deployment takes, the reverse key it added could never reach a core, and the previous commit's claim that the node-push paths go through it was wrong. The four account maps that do reach buildUserAccount are untouched. Reported by the PR review.
55 lines
1.5 KiB
Go
55 lines
1.5 KiB
Go
package xray
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/xtls/xray-core/proxy/vless"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// typedReverse stands in for model.ClientReverse, which this package cannot
|
|
// import (model imports xray); it marshals to the same {"tag":"…"} shape.
|
|
type typedReverse struct {
|
|
Tag string `json:"tag"`
|
|
}
|
|
|
|
// A reverse client's account has to carry its tag: removing the user drops the
|
|
// outbound handler, and only the tag lets the core rebuild it (GetReverse).
|
|
func TestBuildUserAccountCarriesTheReverseTag(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
reverse any
|
|
want string
|
|
}{
|
|
{"settings json shape", map[string]any{"tag": "portal"}, "portal"},
|
|
{"typed client field", &typedReverse{Tag: "portal"}, "portal"},
|
|
{"blank tag", map[string]any{"tag": ""}, ""},
|
|
{"typed nil", (*typedReverse)(nil), ""},
|
|
{"absent", nil, ""},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
user := map[string]any{
|
|
"email": "reverse@example.test",
|
|
"id": "5f2eb9d6-3a2f-4a55-9812-6ea1e2f7a333",
|
|
"reverse": tc.reverse,
|
|
}
|
|
tm, err := buildUserAccount("vless", user)
|
|
if err != nil {
|
|
t.Fatalf("buildUserAccount: %v", err)
|
|
}
|
|
if tm == nil {
|
|
t.Fatal("buildUserAccount returned no account for vless")
|
|
}
|
|
account := new(vless.Account)
|
|
if err := proto.Unmarshal(tm.Value, account); err != nil {
|
|
t.Fatalf("unmarshal vless account: %v", err)
|
|
}
|
|
if got := account.GetReverse().GetTag(); got != tc.want {
|
|
t.Fatalf("the account carries reverse tag %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|