mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-13 00:25:04 +03:00
fix(xray): adopt freedom finalRules delay and logLevel
xray-core 26.11.9 (#7329) renamed a final rule's blockDelay to delay, added logLevel, and now refuses either on a rule whose action is not block. A stored template with a blockDelay on an allow rule therefore stops the core at startup after the upgrade. A one-time seeder strips delay, blockDelay and logLevel from non-block rules of the template's freedom outbounds and moves blockDelay to delay on block rules. The outbound form reads both spellings (blockDelay wins, as in the core), writes delay, gains a log level control on block rules and emits neither key on an allow rule.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/config"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
)
|
||||
|
||||
const legacyFinalRuleDelayTemplate = `{"outbounds":[{"protocol":"freedom","tag":"direct","settings":{"finalRules":[` +
|
||||
`{"action":"allow","ip":["10.0.0.0/8"],"blockDelay":"1000-2000"},` +
|
||||
`{"action":"Block","blockDelay":"5000-10000","delay":"1-2"}]}}]}`
|
||||
|
||||
func finalRulesOfFirstOutbound(t *testing.T, raw string) (json.RawMessage, []any) {
|
||||
t.Helper()
|
||||
var cfg struct {
|
||||
Outbounds []json.RawMessage `json:"outbounds"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
|
||||
t.Fatalf("template is not JSON: %v", err)
|
||||
}
|
||||
var outbound struct {
|
||||
Settings struct {
|
||||
FinalRules []any `json:"finalRules"`
|
||||
} `json:"settings"`
|
||||
}
|
||||
if err := json.Unmarshal(cfg.Outbounds[0], &outbound); err != nil {
|
||||
t.Fatalf("outbound is not JSON: %v", err)
|
||||
}
|
||||
return cfg.Outbounds[0], outbound.Settings.FinalRules
|
||||
}
|
||||
|
||||
func TestRewriteFreedomFinalRuleDelay(t *testing.T) {
|
||||
before, _ := finalRulesOfFirstOutbound(t, legacyFinalRuleDelayTemplate)
|
||||
err := xray.ValidateOutboundConfig(before)
|
||||
if err == nil || !strings.Contains(err.Error(), "only supported for block finalRules") {
|
||||
t.Fatalf("expected the vendored core to refuse a delay on an allow rule, got %v", err)
|
||||
}
|
||||
|
||||
updated, changed, err := rewriteFreedomFinalRuleDelay(legacyFinalRuleDelayTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("rewrite: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Fatal("the allow rule kept the blockDelay the core refuses")
|
||||
}
|
||||
after, rules := finalRulesOfFirstOutbound(t, updated)
|
||||
if err := xray.ValidateOutboundConfig(after); err != nil {
|
||||
t.Fatalf("rewritten outbound still refused by xray-core: %v", err)
|
||||
}
|
||||
want := []any{
|
||||
map[string]any{"action": "allow", "ip": []any{"10.0.0.0/8"}},
|
||||
map[string]any{"action": "Block", "delay": "5000-10000"},
|
||||
}
|
||||
if !reflect.DeepEqual(rules, want) {
|
||||
t.Fatalf("finalRules = %v, want %v", rules, want)
|
||||
}
|
||||
|
||||
if _, again, err := rewriteFreedomFinalRuleDelay(updated); err != nil || again {
|
||||
t.Fatalf("second pass changed=%v err=%v, want a no-op", again, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreedomFinalRuleDelayFixRunsOnUpgradedPanels(t *testing.T) {
|
||||
t.Setenv("XUI_DB_FOLDER", t.TempDir())
|
||||
if err := InitDB(config.GetDBPath()); err != nil {
|
||||
if strings.Contains(err.Error(), "CGO_ENABLED=0") {
|
||||
t.Skipf("sqlite needs cgo: %v", err)
|
||||
}
|
||||
t.Fatalf("init db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = CloseDB() })
|
||||
|
||||
seedTemplate(t, legacyFinalRuleDelayTemplate)
|
||||
if err := db.Where("seeder_name = ?", "FreedomFinalRuleDelayFix").
|
||||
Delete(&model.HistoryOfSeeders{}).Error; err != nil {
|
||||
t.Fatalf("clear seeder history: %v", err)
|
||||
}
|
||||
|
||||
if err := runSeeders(false); err != nil {
|
||||
t.Fatalf("runSeeders: %v", err)
|
||||
}
|
||||
|
||||
outbound, _ := finalRulesOfFirstOutbound(t, storedTemplate(t))
|
||||
if err := xray.ValidateOutboundConfig(outbound); err != nil {
|
||||
t.Fatalf("stored template still refused by xray-core: %v", err)
|
||||
}
|
||||
var count int64
|
||||
if err := db.Model(&model.HistoryOfSeeders{}).
|
||||
Where("seeder_name = ?", "FreedomFinalRuleDelayFix").Count(&count).Error; err != nil {
|
||||
t.Fatalf("count seeder rows: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("FreedomFinalRuleDelayFix recorded %d times, want 1", count)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user