mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 03:05:02 +03:00
fix(inbounds): keep disabled clients out of the core when adding an inbound
The running core must only see clients the panel has enabled, and UpdateInbound and SetInboundEnable already build their payload through buildInboundForLocalRuntime, which strips disabled ones. AddInbound used it only for MTProto and TUIC and hot-added the stored row for every other protocol, so an imported or pushed inbound re-armed its disabled clients until the next restart. AddInbound now builds the runtime payload the same way for every protocol.
This commit is contained in:
@@ -1574,17 +1574,14 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
return perr
|
||||
}
|
||||
if push {
|
||||
payload := inbound
|
||||
pushable := true
|
||||
if inbound.Protocol == model.MTProto || inbound.Protocol == model.TUIC {
|
||||
if built, bErr := s.buildInboundForLocalRuntime(tx, inbound); bErr == nil {
|
||||
payload = built
|
||||
} else {
|
||||
logger.Debug("Unable to prepare runtime inbound config:", bErr)
|
||||
pushable = false
|
||||
// An imported or master-pushed row lists its disabled and depleted clients too.
|
||||
payload, bErr := s.buildInboundForLocalRuntime(tx, inbound)
|
||||
if bErr != nil {
|
||||
logger.Debug("Unable to prepare runtime inbound config:", bErr)
|
||||
if inbound.Protocol != model.MTProto && inbound.Protocol != model.TUIC {
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
if pushable {
|
||||
} else {
|
||||
postCommitApply = func() {
|
||||
if err1 := rt.AddInbound(context.Background(), payload); err1 == nil {
|
||||
logger.Debug("New inbound added on", rt.Name(), ":", inbound.Tag)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
)
|
||||
|
||||
// inboundOpsProbe records every handler and user operation the panel sends the
|
||||
@@ -322,3 +323,55 @@ func TestNodeShadowsocksPushChangingOnlyClientsKeepsTheHandler(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addInboundRecorder keeps the payload handed to the local runtime, so a test can
|
||||
// check what the running core would register rather than what the DB stores.
|
||||
type addInboundRecorder struct {
|
||||
fakeNodeRuntime
|
||||
added *model.Inbound
|
||||
}
|
||||
|
||||
func (r *addInboundRecorder) AddInbound(_ context.Context, ib *model.Inbound) error {
|
||||
r.added = ib
|
||||
return nil
|
||||
}
|
||||
|
||||
// An imported or master-pushed inbound arrives with its clients, switched-off
|
||||
// ones included; only UpdateInbound/SetInboundEnable used to strip them.
|
||||
func TestAddInboundKeepsDisabledClientsOutOfTheCore(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
mgr := useTestRuntimeManager(t)
|
||||
local := &addInboundRecorder{}
|
||||
mgr.SetLocalRuntimeOverride(local)
|
||||
|
||||
svc := &InboundService{}
|
||||
saved, needRestart, err := svc.AddInbound(&model.Inbound{
|
||||
UserId: 1, Enable: true, Port: 44490, Protocol: model.VLESS,
|
||||
StreamSettings: `{"network":"tcp","security":"none"}`,
|
||||
Settings: `{"decryption":"none","clients":[
|
||||
{"email":"paying","id":"5f2eb9d6-3a2f-4a55-9812-6ea1e2f7a001","enable":true},
|
||||
{"email":"banned","id":"5f2eb9d6-3a2f-4a55-9812-6ea1e2f7a002","enable":false},
|
||||
{"email":"depleted","id":"5f2eb9d6-3a2f-4a55-9812-6ea1e2f7a003","enable":true}]}`,
|
||||
ClientStats: []xray.ClientTraffic{{Email: "depleted", Enable: false, Up: 10, Total: 10}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("AddInbound: %v", err)
|
||||
}
|
||||
if needRestart || local.added == nil {
|
||||
t.Fatalf("the inbound must be hot-added to the running core; needRestart=%v added=%v", needRestart, local.added)
|
||||
}
|
||||
served, err := svc.GetClients(local.added)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClients(runtime payload): %v", err)
|
||||
}
|
||||
if len(served) != 1 || served[0].Email != "paying" {
|
||||
t.Fatalf("the core must get only the enabled client, got %s", local.added.Settings)
|
||||
}
|
||||
stored, err := svc.GetClients(saved)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClients(stored row): %v", err)
|
||||
}
|
||||
if len(stored) != 3 {
|
||||
t.Fatalf("the stored row must keep every client, got %s", saved.Settings)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user