mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-31 10:15:00 +03:00
fix(tuic): preserve client identity for Xray user routing (#6875)
TUIC traffic reached Xray through a loopback SOCKS relay that took no login, so Xray never learned which client a connection belonged to and user routing rules never matched TUIC (#6874). The relay now carries one account per client email, so per-user rules match on TCP and both UDP modes; admission stays with the native TUIC server. Adding, removing or renaming a TUIC client swaps only that inbound's relay instead of restarting Xray: new sessions wait behind a bounded gate while live streams and UDP associations continue, accounts are verified after the swap and rolled back on failure, one failing relay no longer strands the others or blocks unrelated live changes, and emails over SOCKS5's 255-byte limit are excluded with a warning.
This commit is contained in:
+23
-10
@@ -14,6 +14,7 @@ import (
|
||||
// process. It only covers the sections Xray can reload at runtime: inbounds,
|
||||
// outbounds and routing rules/balancers.
|
||||
type HotDiff struct {
|
||||
TuicRelayChanges []InboundReplacement
|
||||
RemovedInboundTags []string
|
||||
AddedInbounds [][]byte
|
||||
RemovedUsers []UserOp
|
||||
@@ -26,6 +27,12 @@ type HotDiff struct {
|
||||
RoutingConfig []byte // full new routing section; nil when unchanged
|
||||
}
|
||||
|
||||
// InboundReplacement is verified independently of ordinary hot changes.
|
||||
type InboundReplacement struct {
|
||||
Previous *InboundConfig
|
||||
Next *InboundConfig
|
||||
}
|
||||
|
||||
// UserOp is a per-user AlterInbound operation; User is nil for removals.
|
||||
type UserOp struct {
|
||||
Tag string
|
||||
@@ -57,7 +64,7 @@ func (d *HotDiff) DropsUsers() bool {
|
||||
|
||||
// Empty reports whether the diff contains no operations.
|
||||
func (d *HotDiff) Empty() bool {
|
||||
return len(d.RemovedInboundTags) == 0 &&
|
||||
return len(d.TuicRelayChanges) == 0 && len(d.RemovedInboundTags) == 0 &&
|
||||
len(d.AddedInbounds) == 0 &&
|
||||
len(d.RemovedUsers) == 0 &&
|
||||
len(d.AddedUsers) == 0 &&
|
||||
@@ -146,11 +153,15 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool {
|
||||
if oldIb.Tag == apiTag || oldIb.Tag == "api" {
|
||||
return false
|
||||
}
|
||||
if oldIb.TuicRelay && (!exists || newIb.TuicRelay) {
|
||||
diff.TuicRelayChanges = append(diff.TuicRelayChanges, InboundReplacement{Previous: oldIb, Next: newIb})
|
||||
continue
|
||||
}
|
||||
if exists && (inboundHasReverseClient(oldIb) || inboundHasReverseClient(newIb)) {
|
||||
logger.Debug("hot diff: inbound [", oldIb.Tag, "] carries a reverse-tagged client, forcing a full restart instead of a hot swap")
|
||||
return false
|
||||
}
|
||||
if exists {
|
||||
if exists && !oldIb.TuicRelay {
|
||||
diff.DroppedClients = append(diff.DroppedClients, droppedClients(oldIb, newIb)...)
|
||||
}
|
||||
if exists && diffInboundUsers(oldIb, newIb, diff) {
|
||||
@@ -165,7 +176,7 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool {
|
||||
return false
|
||||
}
|
||||
if exists && (inboundUsesSocksAccounts(oldIb) || inboundUsesSocksAccounts(newIb)) {
|
||||
logger.Debug("hot diff: inbound [", oldIb.Tag, "] is a password-auth SOCKS5 inbound (e.g. internal/amneziawgnet's per-peer relay); a gRPC remove+add reports success but was observed in production to silently drop an account, forcing a full restart instead of a hot swap")
|
||||
logger.Debug("hot diff: inbound [", oldIb.Tag, "] is a password-auth SOCKS5 inbound (e.g. TUIC or AmneziaWG relay); a gRPC remove+add reports success but was observed in production to silently drop an account, forcing a full restart instead of a hot swap")
|
||||
return false
|
||||
}
|
||||
diff.RemovedInboundTags = append(diff.RemovedInboundTags, oldIb.Tag)
|
||||
@@ -185,12 +196,16 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool {
|
||||
if newIb.Tag == apiTag || newIb.Tag == "api" {
|
||||
return false
|
||||
}
|
||||
if newIb.TuicRelay {
|
||||
diff.TuicRelayChanges = append(diff.TuicRelayChanges, InboundReplacement{Next: newIb})
|
||||
continue
|
||||
}
|
||||
if inboundUsesTproxy(newIb) {
|
||||
logger.Debug("hot diff: new inbound [", newIb.Tag, "] is a TPROXY target (e.g. internal/amneziawg's Xray egress bridge); a gRPC add reports success but does not reliably bind a working listener, forcing a full restart instead of a hot add")
|
||||
return false
|
||||
}
|
||||
if inboundUsesSocksAccounts(newIb) {
|
||||
logger.Debug("hot diff: new inbound [", newIb.Tag, "] is a password-auth SOCKS5 inbound (e.g. internal/amneziawgnet's per-peer relay); forcing a full restart instead of a hot add, same reasoning as the existing-inbound case above")
|
||||
logger.Debug("hot diff: new inbound [", newIb.Tag, "] is a password-auth SOCKS5 inbound (e.g. TUIC or AmneziaWG relay); forcing a full restart instead of a hot add, same reasoning as the existing-inbound case above")
|
||||
return false
|
||||
}
|
||||
raw, err := json.Marshal(newIb)
|
||||
@@ -384,12 +399,10 @@ func inboundUsesTproxy(ib *InboundConfig) bool {
|
||||
}
|
||||
|
||||
// inboundUsesSocksAccounts reports whether an inbound is a password-auth
|
||||
// SOCKS5 inbound with one or more named accounts -- the shape
|
||||
// internal/amneziawgnet's per-inbound relay (internal/web/service/xray.go's
|
||||
// injectAmneziawgnetSocks) is the only generator of in this fork; every
|
||||
// other SOCKS5 bridge this fork builds (panel egress, per-node egress,
|
||||
// mtproto egress) uses "noauth" with no per-account identity at all, so this
|
||||
// check can't accidentally rope in one of those lower-churn bridges.
|
||||
// SOCKS5 inbound. TUIC and AmneziaWG generate this shape through
|
||||
// injectTuicRelay and injectAmneziawgnetSocks in internal/web/service/xray.go.
|
||||
// The panel, per-node and MTProto egress bridges use "noauth" without
|
||||
// per-account identity, so this check does not affect those bridges.
|
||||
//
|
||||
// Real production incident, not a theoretical concern: a single client
|
||||
// edit under an AmneziaWG inbound left this inbound's settings unchanged in
|
||||
|
||||
Reference in New Issue
Block a user