mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +03:00
fix(xray): refuse geodata settings xray-core 26.10.29 refuses at startup
xray-core 26.10.29 (#7267) checks a scheduled geodata section when the core starts: at most 100 assets, every file a .dat, a cron it can parse and an @every interval of at least a minute. A refusal there stops the core and every inbound with it, and the panel's own file-name pattern still let any extension through. The geodata editor now requires a .dat file name, and the template save path applies the same checks the core does. The invalid-file hint names the .dat requirement in all 14 locales.
This commit is contained in:
@@ -5,9 +5,13 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
@@ -81,6 +85,9 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
||||
if err := checkTemplateLuaScripts(xrayConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkTemplateGeodata([]byte(xrayConfig.Geodata)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, inbound := range xrayConfig.InboundConfigs {
|
||||
if err := validateFinalMaskSalamander(string(inbound.StreamSettings)); err != nil {
|
||||
return common.NewError("xray template inbound \""+inbound.Tag+"\":", err)
|
||||
@@ -158,6 +165,36 @@ func checkTemplateLuaScripts(cfg *xray.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkTemplateGeodata applies the checks xray-core 26.10.29 (#7267) runs on a scheduled geodata
|
||||
// section only when the core starts, where a refusal stops every inbound.
|
||||
func checkTemplateGeodata(raw []byte) error {
|
||||
var geodata struct {
|
||||
Cron string `json:"cron"`
|
||||
Assets []struct {
|
||||
File string `json:"file"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
if len(raw) == 0 || json.Unmarshal(raw, &geodata) != nil || geodata.Cron == "" {
|
||||
return nil
|
||||
}
|
||||
if len(geodata.Assets) > 100 {
|
||||
return common.NewError("xray template geodata: xray-core supports at most 100 assets.")
|
||||
}
|
||||
for _, asset := range geodata.Assets {
|
||||
if !strings.EqualFold(filepath.Ext(asset.File), ".dat") {
|
||||
return common.NewError(`xray template geodata: file extension must be .dat, not "` + asset.File + `".`)
|
||||
}
|
||||
}
|
||||
schedule, err := cron.ParseStandard(geodata.Cron)
|
||||
if err != nil {
|
||||
return common.NewError("xray template geodata: invalid geodata cron:", err)
|
||||
}
|
||||
if interval, ok := schedule.(cron.ConstantDelaySchedule); ok && interval.Delay < time.Minute {
|
||||
return common.NewError("xray template geodata: the cron interval must be at least 1 minute.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldSkipLegacyUnencryptedOutboundRejection keeps an unencrypted outbound the running core still
|
||||
// allows: any address before 26.7.11, a legacy-private IP before 26.10.29; unknown versions stay strict.
|
||||
func shouldSkipLegacyUnencryptedOutboundRejection(coreVersion string, outbound []byte, err error) bool {
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -289,6 +290,44 @@ func TestCheckXrayConfig_RefusesLuaScriptsTheCoreRefuses(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// xray-core 26.10.29 (#7267) refuses these geodata settings when the core starts, stopping every inbound.
|
||||
func TestCheckXrayConfig_RefusesGeodataTheCoreRefuses(t *testing.T) {
|
||||
const outbounds = `"outbounds":[{"protocol":"freedom","tag":"direct"}]`
|
||||
geodata := func(cron string, files ...string) string {
|
||||
assets := make([]string, len(files))
|
||||
for i, f := range files {
|
||||
assets[i] = `{"url":"https://example.com/` + f + `","file":"` + f + `"}`
|
||||
}
|
||||
return `{"geodata":{"cron":"` + cron + `","assets":[` + strings.Join(assets, ",") + `]},` + outbounds + `}`
|
||||
}
|
||||
tooMany := make([]string, 101)
|
||||
for i := range tooMany {
|
||||
tooMany[i] = "geo" + strconv.Itoa(i) + ".dat"
|
||||
}
|
||||
tests := []struct{ name, template, want string }{
|
||||
{"file that is not .dat", geodata("0 4 * * *", "custom.mmdb"), "file extension must be .dat"},
|
||||
{"more than 100 assets", geodata("0 4 * * *", tooMany...), "at most 100 assets"},
|
||||
{"cron the core cannot parse", geodata("every day", "custom.dat"), "invalid geodata cron"},
|
||||
{"interval under a minute", geodata("@every 30s", "custom.dat"), "at least 1 minute"},
|
||||
{"upper-case extension on a daily cron", geodata("0 4 * * *", "Custom.DAT"), ""},
|
||||
{"no cron leaves the assets unscheduled", geodata("", "custom.mmdb"), ""},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := (&XraySettingService{}).CheckXrayConfig(tc.template)
|
||||
if tc.want == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("CheckXrayConfig = %v, want the geodata the core accepts kept", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("CheckXrayConfig = %v, want one containing %q", err, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A template saved under 26.10.10 can carry those scripts; the build drops them so the core still
|
||||
// starts, and the vendored loader accepts the routing and dns sections it then emits.
|
||||
func TestGetXrayConfig_DropsLuaScriptsTheCoreRefuses(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user