fix(clients): store flow per-inbound for shared clients

A client shared across inbounds (e.g. VLESS+TCP+Reality and VLESS+WS+TLS)
had its `flow` applied globally, so enabling xtls-rprx-vision for Reality
broke the WS+TLS inbound for the same client (#4628).

Gate flow per inbound at every fan-out site via clientWithInboundFlow,
reusing inboundCanEnableTlsFlow (VLESS+TCP+TLS/Reality only), and make
ListForInbound treat flow_override as authoritative so an empty override
means "no flow on this inbound" instead of inheriting the record's global
flow. Also tighten buildTargetClientFromSource (copy-clients) to gate on
transport, not just protocol.
This commit is contained in:
MHSanaei
2026-05-29 02:35:53 +02:00
parent 8e301dbca9
commit 7ea88e3e37
3 changed files with 102 additions and 31 deletions
+5 -3
View File
@@ -1074,7 +1074,7 @@ func (s *InboundService) generateRandomCredential(targetProtocol model.Protocol)
}
}
func (s *InboundService) buildTargetClientFromSource(source model.Client, targetProtocol model.Protocol, email string, flow string) (model.Client, error) {
func (s *InboundService) buildTargetClientFromSource(source model.Client, targetInbound *model.Inbound, email string, flow string) (model.Client, error) {
nowTs := time.Now().UnixMilli()
target := source
target.Email = email
@@ -1086,12 +1086,14 @@ func (s *InboundService) buildTargetClientFromSource(source model.Client, target
target.Auth = ""
target.Flow = ""
targetProtocol := targetInbound.Protocol
switch targetProtocol {
case model.VMESS:
target.ID = s.generateRandomCredential(targetProtocol)
case model.VLESS:
target.ID = s.generateRandomCredential(targetProtocol)
if flow == "xtls-rprx-vision" || flow == "xtls-rprx-vision-udp443" {
if (flow == "xtls-rprx-vision" || flow == "xtls-rprx-vision-udp443") &&
inboundCanEnableTlsFlow(string(targetProtocol), targetInbound.StreamSettings) {
target.Flow = flow
}
case model.Trojan, model.Shadowsocks:
@@ -1192,7 +1194,7 @@ func (s *InboundService) CopyInboundClients(targetInboundID int, sourceInboundID
}
targetEmail := s.nextAvailableCopiedEmail(originalEmail, targetInboundID, occupiedEmails)
targetClient, buildErr := s.buildTargetClientFromSource(sourceClient, targetInbound.Protocol, targetEmail, flow)
targetClient, buildErr := s.buildTargetClientFromSource(sourceClient, targetInbound, targetEmail, flow)
if buildErr != nil {
result.Errors = append(result.Errors, fmt.Sprintf("%s: %v", originalEmail, buildErr))
continue