From c2a2a36f5685bd90ce1a9eb8b1b09c5dd24bf6c2 Mon Sep 17 00:00:00 2001 From: Troodi Date: Sun, 19 Apr 2026 22:44:51 +0300 Subject: [PATCH] Fix geosite:ru rule (Normalization to RU vs lowercase ru) (#3971) * Fix geosite:ru rule (Normalization to RU vs lowercase ru) * fix --- go.mod | 2 +- web/service/server.go | 56 +++++++++++++++++++++++++++++++++++++++++++ web/web.go | 18 ++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a51dc36b..a30157c3 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( golang.org/x/sys v0.42.0 golang.org/x/text v0.35.0 google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 ) @@ -96,7 +97,6 @@ require ( golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/protobuf v1.36.11 // indirect gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect lukechampine.com/blake3 v1.4.1 // indirect ) diff --git a/web/service/server.go b/web/service/server.go index 2e0b34b8..6685ceab 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -34,6 +34,8 @@ import ( "github.com/shirou/gopsutil/v4/load" "github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/net" + "github.com/xtls/xray-core/app/router" + "google.golang.org/protobuf/proto" ) // ProcessState represents the current state of a system process. @@ -1055,6 +1057,48 @@ func (s *ServerService) IsValidGeofileName(filename string) bool { return matched } +// NormalizeGeositeCountryCodes reads a geosite .dat file, uppercases all +// country_code fields, and writes it back. This works around a case-sensitivity +// mismatch in Xray-core: the router normalizes codes to uppercase before lookup, +// but the find() function compares bytes case-sensitively. Some geosite.dat +// providers (e.g. Loyalsoldier) store codes in lowercase, causing lookup failures. +func NormalizeGeositeCountryCodes(path string) error { + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read geosite file %s: %w", path, err) + } + + var list router.GeoSiteList + if err := proto.Unmarshal(data, &list); err != nil { + return fmt.Errorf("failed to parse geosite file %s: %w", path, err) + } + + changed := false + for _, entry := range list.Entry { + upper := strings.ToUpper(entry.CountryCode) + if entry.CountryCode != upper { + entry.CountryCode = upper + changed = true + } + } + + if !changed { + return nil + } + + normalized, err := proto.Marshal(&list) + if err != nil { + return fmt.Errorf("failed to serialize normalized geosite file %s: %w", path, err) + } + + if err := os.WriteFile(path, normalized, 0o644); err != nil { + return fmt.Errorf("failed to write normalized geosite file %s: %w", path, err) + } + + logger.Infof("Normalized country codes to uppercase in %s (%d entries)", path, len(list.Entry)) + return nil +} + func (s *ServerService) UpdateGeofile(fileName string) error { type geofileEntry struct { URL string @@ -1146,12 +1190,22 @@ func (s *ServerService) UpdateGeofile(fileName string) error { var errorMessages []string + normalizeIfGeosite := func(destPath, name string) { + if strings.Contains(name, "geosite") { + if err := NormalizeGeositeCountryCodes(destPath); err != nil { + logger.Warningf("Failed to normalize geosite country codes in %s: %v", name, err) + } + } + } + if fileName == "" { // Download all geofiles for _, entry := range geofileAllowlist { destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName) if err := downloadFile(entry.URL, destPath); err != nil { errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err)) + } else { + normalizeIfGeosite(destPath, entry.FileName) } } } else { @@ -1159,6 +1213,8 @@ func (s *ServerService) UpdateGeofile(fileName string) error { destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName) if err := downloadFile(entry.URL, destPath); err != nil { errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err)) + } else { + normalizeIfGeosite(destPath, entry.FileName) } } diff --git a/web/web.go b/web/web.go index 158bc048..47f58beb 100644 --- a/web/web.go +++ b/web/web.go @@ -12,6 +12,7 @@ import ( "net" "net/http" "os" + "path/filepath" "strconv" "strings" "time" @@ -293,9 +294,26 @@ func (s *Server) initRouter() (*gin.Engine, error) { return engine, nil } +// normalizeExistingGeositeFiles normalizes country codes in all geosite .dat +// files found in the bin directory so Xray-core can locate entries correctly. +func normalizeExistingGeositeFiles() { + binDir := config.GetBinFolderPath() + matches, err := filepath.Glob(filepath.Join(binDir, "geosite*.dat")) + if err != nil { + logger.Warningf("Failed to glob geosite files: %v", err) + return + } + for _, path := range matches { + if err := service.NormalizeGeositeCountryCodes(path); err != nil { + logger.Warningf("Failed to normalize geosite country codes in %s: %v", path, err) + } + } +} + // startTask schedules background jobs (Xray checks, traffic jobs, cron // jobs) which the panel relies on for periodic maintenance and monitoring. func (s *Server) startTask() { + normalizeExistingGeositeFiles() s.customGeoService.EnsureOnStartup() err := s.xrayService.RestartXray(true) if err != nil {