mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
fix(xray): read an outbound protocol id the way the core does (#6521)
* fix(xray): read an outbound protocol id the way the core does xray-core lowercases a protocol id before it looks up the handler (infra/conf/loader.go: `id = strings.ToLower(id)`), so a template that spells the direct outbound "Freedom" runs as freedom. The three config rewriters compared the id case-sensitively, so such an outbound was skipped while the seed was recorded as applied: the refused sockopt.addressPortStrategy stayed and xray-core refused to start. * fix(xray): match an outbound protocol id case-insensitively xray-core lowercases a protocol id before looking up its handler, so a template that spells the direct outbound "Freedom" runs as freedom while this rewriter skipped it and left the deprecated placement in place. * fix(xray): re-run the freedom finalRules rewrite where its seeder was gated Both finalRules seeders recorded their rows before the predicate could see an outbound spelled "Freedom", and a recorded row is never re-run, so the corrected predicates alone left the #6037 private-egress hardening unapplied on every panel that had already run them. The new one-shot seeder replays both rewrites, and only when the config actually carries a differently spelled freedom outbound, so stock lowercase configs stay byte-identical.
This commit is contained in:
+82
-5
@@ -1254,7 +1254,7 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
}
|
||||
|
||||
if empty && isUsersEmpty {
|
||||
seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix2", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "FreedomFinalRulesPrivateEgressBlock", "InboundRealityFinalmaskTcpStrip", "ApiTokensHash", "LegacyProxySettingsCleanup", "OutboundRemovedKeysFix", "FreedomDomainStrategyFix", "DNSOutboundLegacyKeysFix", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted", "ResetIpLimitNoFail2ban"}
|
||||
seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix2", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "FreedomFinalRulesPrivateEgressBlock", "UppercaseFreedomFinalRulesFix", "InboundRealityFinalmaskTcpStrip", "ApiTokensHash", "LegacyProxySettingsCleanup", "OutboundRemovedKeysFix", "FreedomDomainStrategyFix", "DNSOutboundLegacyKeysFix", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted", "ResetIpLimitNoFail2ban"}
|
||||
for _, name := range seeders {
|
||||
if err := db.Create(&model.HistoryOfSeeders{SeederName: name}).Error; err != nil {
|
||||
return err
|
||||
@@ -1347,6 +1347,12 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Contains(seedersHistory, "UppercaseFreedomFinalRulesFix") {
|
||||
if err := fixUppercaseFreedomFinalRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Contains(seedersHistory, "InboundRealityFinalmaskTcpStrip") {
|
||||
if err := stripRealityFinalmaskTcp(); err != nil {
|
||||
return err
|
||||
@@ -1625,7 +1631,7 @@ func rewriteRemovedOutboundKeys(raw string) (string, bool, error) {
|
||||
delete(obj, "proxySettings")
|
||||
changed = true
|
||||
}
|
||||
if proto, _ := obj["protocol"].(string); proto == "freedom" {
|
||||
if proto, _ := obj["protocol"].(string); strings.EqualFold(proto, "freedom") {
|
||||
if sockopt := outboundSockopt(obj, false); sockopt != nil {
|
||||
if _, present := sockopt["addressPortStrategy"]; present {
|
||||
delete(sockopt, "addressPortStrategy")
|
||||
@@ -1711,7 +1717,7 @@ func rewriteFreedomDomainStrategy(raw string) (string, bool, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if proto, _ := obj["protocol"].(string); proto != "freedom" {
|
||||
if proto, _ := obj["protocol"].(string); !strings.EqualFold(proto, "freedom") {
|
||||
continue
|
||||
}
|
||||
settings, hasSettings := obj["settings"].(map[string]any)
|
||||
@@ -2144,7 +2150,7 @@ func rewriteFreedomFinalRules(raw string) (string, bool, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if proto, _ := obj["protocol"].(string); proto != "freedom" {
|
||||
if proto, _ := obj["protocol"].(string); !strings.EqualFold(proto, "freedom") {
|
||||
continue
|
||||
}
|
||||
settings, ok := obj["settings"].(map[string]any)
|
||||
@@ -2247,7 +2253,7 @@ func rewriteFreedomFinalRulesPrivateEgress(raw string) (string, bool, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if proto, _ := obj["protocol"].(string); proto != "freedom" {
|
||||
if proto, _ := obj["protocol"].(string); !strings.EqualFold(proto, "freedom") {
|
||||
continue
|
||||
}
|
||||
settings, ok := obj["settings"].(map[string]any)
|
||||
@@ -2276,6 +2282,77 @@ func rewriteFreedomFinalRulesPrivateEgress(raw string) (string, bool, error) {
|
||||
return string(out), true, nil
|
||||
}
|
||||
|
||||
func fixUppercaseFreedomFinalRules() error {
|
||||
var setting model.Setting
|
||||
err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(&setting).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return db.Create(&model.HistoryOfSeeders{SeederName: "UppercaseFreedomFinalRulesFix"}).Error
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updated, changed, rErr := rewriteUppercaseFreedomFinalRules(setting.Value)
|
||||
if rErr != nil {
|
||||
log.Printf("UppercaseFreedomFinalRulesFix: skip (invalid xrayTemplateConfig json): %v", rErr)
|
||||
return db.Create(&model.HistoryOfSeeders{SeederName: "UppercaseFreedomFinalRulesFix"}).Error
|
||||
}
|
||||
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if changed {
|
||||
if err := tx.Model(&model.Setting{}).Where("key = ?", "xrayTemplateConfig").
|
||||
Update("value", updated).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(&model.HistoryOfSeeders{SeederName: "UppercaseFreedomFinalRulesFix"}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// Re-runs both finalRules rewrites, because the rows of the two seeders above it
|
||||
// already exist on any panel that walked past a differently spelled outbound.
|
||||
func rewriteUppercaseFreedomFinalRules(raw string) (string, bool, error) {
|
||||
if !hasNonLowercaseFreedomOutbound(raw) {
|
||||
return raw, false, nil
|
||||
}
|
||||
reversed, reversedChanged, err := rewriteFreedomFinalRules(raw)
|
||||
if err != nil {
|
||||
return raw, false, err
|
||||
}
|
||||
hardened, hardenedChanged, err := rewriteFreedomFinalRulesPrivateEgress(reversed)
|
||||
if err != nil {
|
||||
return raw, false, err
|
||||
}
|
||||
if !reversedChanged && !hardenedChanged {
|
||||
return raw, false, nil
|
||||
}
|
||||
return hardened, true, nil
|
||||
}
|
||||
|
||||
func hasNonLowercaseFreedomOutbound(raw string) bool {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return false
|
||||
}
|
||||
var cfg map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
|
||||
return false
|
||||
}
|
||||
outbounds, ok := cfg["outbounds"].([]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, ob := range outbounds {
|
||||
obj, ok := ob.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if proto, _ := obj["protocol"].(string); strings.EqualFold(proto, "freedom") && proto != "freedom" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stripRealityFinalmaskTcp() error {
|
||||
var inbounds []model.Inbound
|
||||
if err := db.Find(&inbounds).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user