Pārlūkot izejas kodu

fix(migrate): relax legacy freedom finalRules so reverse egress works on existing installs

The d414e186 template change only helps fresh configs; installs already on xray-core >=26.5 keep their stored finalRules of [{allow, geoip:private}], which blocks WAN egress for reverse-proxy traffic (refs #4782, XTLS/Xray-core#6248).

Add a FreedomFinalRulesReverseFix seeder that, on startup, rewrites any freedom outbound whose finalRules is exactly [{allow, ip:[geoip:private]}] to a no-condition [{allow}]. The match is exact, so custom rules (extra keys, other IPs, block actions, multiple rules) are left untouched. Runs once via history_of_seeders and is skipped on fresh installs.
MHSanaei 14 stundas atpakaļ
vecāks
revīzija
6f6c7fc17a
1 mainītis faili ar 102 papildinājumiem un 1 dzēšanām
  1. 102 1
      database/db.go

+ 102 - 1
database/db.go

@@ -181,7 +181,7 @@ func runSeeders(isUsersEmpty bool) error {
 	}
 
 	if empty && isUsersEmpty {
-		seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix", "InboundClientSubIdFix"}
+		seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix"}
 		for _, name := range seeders {
 			if err := db.Create(&model.HistoryOfSeeders{SeederName: name}).Error; err != nil {
 				return err
@@ -255,6 +255,12 @@ func runSeeders(isUsersEmpty bool) error {
 			return err
 		}
 	}
+
+	if !slices.Contains(seedersHistory, "FreedomFinalRulesReverseFix") {
+		if err := normalizeFreedomFinalRules(); err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -401,6 +407,101 @@ func normalizeInboundClientsArray() error {
 	})
 }
 
+func normalizeFreedomFinalRules() 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: "FreedomFinalRulesReverseFix"}).Error
+	}
+	if err != nil {
+		return err
+	}
+
+	updated, changed, rErr := rewriteFreedomFinalRules(setting.Value)
+	if rErr != nil {
+		log.Printf("FreedomFinalRulesReverseFix: skip (invalid xrayTemplateConfig json): %v", rErr)
+		return db.Create(&model.HistoryOfSeeders{SeederName: "FreedomFinalRulesReverseFix"}).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: "FreedomFinalRulesReverseFix"}).Error
+	})
+}
+
+func rewriteFreedomFinalRules(raw string) (string, bool, error) {
+	if strings.TrimSpace(raw) == "" {
+		return raw, false, nil
+	}
+	var cfg map[string]any
+	if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
+		return raw, false, err
+	}
+	outbounds, ok := cfg["outbounds"].([]any)
+	if !ok {
+		return raw, false, nil
+	}
+	changed := false
+	for _, ob := range outbounds {
+		obj, ok := ob.(map[string]any)
+		if !ok {
+			continue
+		}
+		if proto, _ := obj["protocol"].(string); proto != "freedom" {
+			continue
+		}
+		settings, ok := obj["settings"].(map[string]any)
+		if !ok {
+			continue
+		}
+		if !isLegacyPrivateOnlyFinalRules(settings["finalRules"]) {
+			continue
+		}
+		settings["finalRules"] = []any{map[string]any{"action": "allow"}}
+		changed = true
+	}
+	if !changed {
+		return raw, false, nil
+	}
+	out, err := json.MarshalIndent(cfg, "", "  ")
+	if err != nil {
+		return raw, false, err
+	}
+	return string(out), true, nil
+}
+
+func isLegacyPrivateOnlyFinalRules(v any) bool {
+	rules, ok := v.([]any)
+	if !ok || len(rules) != 1 {
+		return false
+	}
+	rule, ok := rules[0].(map[string]any)
+	if !ok {
+		return false
+	}
+	if action, _ := rule["action"].(string); action != "allow" {
+		return false
+	}
+	ips, ok := rule["ip"].([]any)
+	if !ok || len(ips) != 1 {
+		return false
+	}
+	if s, _ := ips[0].(string); s != "geoip:private" {
+		return false
+	}
+	for k := range rule {
+		if k != "action" && k != "ip" {
+			return false
+		}
+	}
+	return true
+}
+
 // normalizeClientJSONFields coerces loosely-typed numeric fields in a raw
 // settings.clients entry so json.Unmarshal into model.Client doesn't fail
 // when older rows wrote tgId/limitIp/totalGB/etc. as strings. Empty strings