mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-26 21:16:57 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e76d009b8 |
+3
-11
@@ -21,10 +21,7 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const (
|
||||
idleTimeout = 30 * time.Second
|
||||
maxDownloadSize int64 = 256 * 1024 * 1024
|
||||
)
|
||||
const idleTimeout = 30 * time.Second
|
||||
|
||||
// DO NOT EDIT. Ignored by geodata, rejected by Lua.
|
||||
const luaGuardHeader = "\x12\x01\x00"
|
||||
@@ -219,19 +216,14 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
return errors.New("unexpected status code: ", resp.StatusCode)
|
||||
}
|
||||
if resp.ContentLength > maxDownloadSize {
|
||||
return errors.New("geodata download exceeds 256 MiB")
|
||||
}
|
||||
|
||||
n, err := io.Copy(writer, io.LimitReader(resp.Body, maxDownloadSize+1))
|
||||
n, err := io.Copy(writer, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > maxDownloadSize {
|
||||
return errors.New("geodata download exceeds 256 MiB")
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.New("empty response body")
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||
xlua "github.com/xtls/xray-core/common/lua"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/yuin/gopher-lua/parse"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -74,53 +71,3 @@ func TestDownloadOneLuaGuard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtobufConfigValidation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("xray.location.asset", dir)
|
||||
for _, name := range []string{"asset.dat", "asset.DAT"} {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(dir, "directory.dat"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := core.New(&core.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = server.Close() })
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
config *Config
|
||||
wantError string
|
||||
}{
|
||||
{"minutely cron", &Config{Cron: "* * * * *"}, ""},
|
||||
{"minimum interval", &Config{Cron: "@every 1m"}, ""},
|
||||
{"short interval", &Config{Cron: "@every 59s"}, "interval must be at least 1 minute"},
|
||||
{"fractional interval", &Config{Cron: "@every 59.999s"}, "interval must be at least 1 minute"},
|
||||
{"invalid cron", &Config{Cron: "invalid"}, "invalid geodata cron"},
|
||||
{"dat extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.dat"}}}, ""},
|
||||
{"uppercase extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.DAT"}}}, ""},
|
||||
{"invalid extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.lua"}}}, "extension must be .dat"},
|
||||
{"missing extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset"}}}, "extension must be .dat"},
|
||||
{"missing file", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "missing.dat"}}}, "invalid geodata asset file"},
|
||||
{"directory", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "directory.dat"}}}, "asset is not a regular file"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
config, err := serial.ToTypedMessage(tc.config).GetInstance()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = core.CreateObject(server, config)
|
||||
if tc.wantError == "" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if err == nil || !strings.Contains(err.Error(), tc.wantError) {
|
||||
t.Fatalf("CreateObject() error = %v; want %q", err, tc.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+3
-23
@@ -2,16 +2,12 @@ package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
@@ -29,24 +25,6 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
if config.Cron == "" {
|
||||
return &Instance{}, nil
|
||||
}
|
||||
if len(config.Assets) > 100 {
|
||||
return nil, errors.New("geodata supports at most 100 assets")
|
||||
}
|
||||
for _, asset := range config.Assets {
|
||||
if !strings.EqualFold(filepath.Ext(asset.GetFile()), ".dat") {
|
||||
return nil, errors.New("geodata asset file extension must be .dat")
|
||||
}
|
||||
if _, err := filesystem.StatAsset(asset.GetFile()); err != nil {
|
||||
return nil, errors.New("invalid geodata asset file: ", asset.GetFile()).Base(err)
|
||||
}
|
||||
}
|
||||
schedule, err := cron.ParseStandard(config.Cron)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid geodata cron").Base(err)
|
||||
}
|
||||
if interval, ok := schedule.(cron.ConstantDelaySchedule); ok && interval.Delay < time.Minute {
|
||||
return nil, errors.New("geodata cron interval must be at least 1 minute")
|
||||
}
|
||||
|
||||
g := &Instance{
|
||||
assets: config.Assets,
|
||||
@@ -66,7 +44,9 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
|
||||
cron.WithLogger(cron.DiscardLogger),
|
||||
)
|
||||
g.tasker.Schedule(schedule, cron.FuncJob(g.execute))
|
||||
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil {
|
||||
return nil, errors.New("invalid geodata cron").Base(err)
|
||||
}
|
||||
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
|
||||
|
||||
return g, nil
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/miekg/dns v1.1.73
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pion/stun/v3 v3.1.7
|
||||
github.com/pires/go-proxyproto v0.15.0
|
||||
github.com/pires/go-proxyproto v0.16.0
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
|
||||
@@ -59,8 +59,8 @@ github.com/pion/stun/v3 v3.1.7 h1:uRXMTlGLf89WgItGNyZ6aR5jMTX0NBbybXADpQCzn+E=
|
||||
github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8=
|
||||
github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
|
||||
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
||||
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||
github.com/pires/go-proxyproto v0.16.0 h1:9KYT/fM3wpjT8bdMEj/7FBcoiUG1KyzEjRYnPu+vz7A=
|
||||
github.com/pires/go-proxyproto v0.16.0/go.mod h1:n7yhg8yIfNl3KgYDCwBzmeUHNpKTVnUhcWpc7rV4RW4=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/xtls/xray-core/app/geodata"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,9 @@ func (c *GeodataAssetConfig) Build() (*geodata.Asset, error) {
|
||||
if err := validateHTTPS(c.URL); err != nil {
|
||||
return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err)
|
||||
}
|
||||
if _, err := filesystem.StatAsset(c.File); err != nil {
|
||||
return nil, errors.New("invalid geodata asset file: ", c.File).Base(err)
|
||||
}
|
||||
return &geodata.Asset{
|
||||
Url: c.URL,
|
||||
File: c.File,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/app/geodata"
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestGeodataConfig(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
creator := func() Buildable {
|
||||
return new(GeodataConfig)
|
||||
}
|
||||
@@ -35,7 +38,27 @@ func TestGeodataConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGeodataAssetConfig(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
if _, err := (&GeodataAssetConfig{
|
||||
URL: "https://example.com/geoip.dat",
|
||||
File: "geoip.dat",
|
||||
}).Build(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := (&GeodataAssetConfig{
|
||||
URL: "https://example.com/geoip.dat",
|
||||
File: "missing.dat",
|
||||
}).Build(); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeodataAssetConfigInvalidURL(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
for _, rawURL := range []string{
|
||||
"",
|
||||
"http://example.com/geoip.dat",
|
||||
|
||||
Reference in New Issue
Block a user