mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-09 14:16:07 +00:00
fix(inbound): scope port-conflict check to the stored node on update (#5833)
* fix(inbound): scope port-conflict check to the stored node on update UpdateInbound called checkPortConflict before restoring the inbound's NodeID from the database, so the check used the NodeID from the request body. That value is unreliable for edits: clients omit it (nodeId is `json:",omitempty"`) and the code already treats the stored NodeID as authoritative — an inbound can't be moved between nodes via edit. With a nil request NodeID a node inbound was mis-checked as a local/main-panel inbound and falsely collided with an unrelated inbound that happened to reuse the same port on the central panel (or another node). Symptom: editing a node inbound's listen address was rejected with "port <p> (tcp) already used by inbound ... " and silently discarded. Load the old inbound and restore inbound.NodeID *before* checkPortConflict, so the check runs against the node the inbound actually lives on. checkPortConflict already scopes candidates by node (sameNode); it was simply being fed the wrong NodeID. Add a regression test that seeds a main-panel and a node inbound on the same port and asserts the node inbound stays editable (fails before this change with the exact "already used" rejection). * style(inbound): trim inline comments from port-conflict scoping Repo convention forbids // line comments in committed Go; keep the scoping fix self-documenting.
This commit is contained in:
committed by
GitHub
parent
f9cd7ac906
commit
2c28fa5f48
@@ -1104,6 +1104,14 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
s.normalizeMtprotoSecret(inbound)
|
||||
inbound.SubSortIndex = normalizeSubSortIndex(inbound.SubSortIndex)
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
if err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
// Restore the stored NodeID before the port-conflict check so a node inbound
|
||||
// stays scoped to its own node (the payload's nodeId is unreliable, often absent).
|
||||
inbound.NodeID = oldInbound.NodeID
|
||||
|
||||
conflict, err := s.checkPortConflict(inbound, inbound.Id)
|
||||
if err != nil {
|
||||
return inbound, false, err
|
||||
@@ -1112,11 +1120,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
return inbound, false, common.NewError(conflict.String())
|
||||
}
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
if err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
inbound.NodeID = oldInbound.NodeID
|
||||
// Capture the pre-edit protocol and routing state before oldInbound is
|
||||
// overwritten with the new values further down, then ensure a routed
|
||||
// inbound keeps a stable egress port (reusing the one already stored).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
@@ -67,6 +68,29 @@ func TestUpdateInbound_NodeTagKeepsPrefixWhenNodeIdOmitted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A node inbound sharing a port with a local inbound must stay editable: the
|
||||
// port-conflict check is scoped to the inbound's stored NodeID, not the body's.
|
||||
func TestUpdateInbound_NodeInboundNotBlockedByLocalSamePort(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
seedInboundConflict(t, "in-10000-tcp", "0.0.0.0", 10000, model.VLESS, `{"network":"tcp"}`, `{"clients":[]}`)
|
||||
seedInboundConflictNode(t, "n1-in-10000-tcp", "0.0.0.0", 10000, model.VLESS, `{"network":"tcp"}`, `{"clients":[]}`, new(1))
|
||||
|
||||
var nodeInbound model.Inbound
|
||||
if err := database.GetDB().Where("tag = ?", "n1-in-10000-tcp").First(&nodeInbound).Error; err != nil {
|
||||
t.Fatalf("read seeded node row: %v", err)
|
||||
}
|
||||
|
||||
svc := &InboundService{}
|
||||
update := nodeInbound
|
||||
update.Listen = "10.0.0.5"
|
||||
update.NodeID = nil
|
||||
_, _, err := svc.UpdateInbound(&update)
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "already used") {
|
||||
t.Fatalf("node inbound edit wrongly rejected as a port conflict: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// a tag the user set by hand (doesn't match the canonical shape) survives a
|
||||
// port change untouched.
|
||||
func TestUpdateInbound_KeepsCustomTagOnPortChange(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user