yiguodev
2026-07-10 10:56:49 +08:00
committed by GitHub
parent c18b39ed80
commit d5bc58dc6b
15 changed files with 241 additions and 52 deletions
+1
View File
@@ -37,6 +37,7 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
# Create log files # Create log files
+1
View File
@@ -37,6 +37,7 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
# Create log files # Create log files
+1 -1
View File
@@ -122,7 +122,7 @@ func NewReader(reader io.Reader) Reader {
} }
_, isFile := reader.(*os.File) _, isFile := reader.(*os.File)
if !isFile && useReadv { if !isFile && useReadV() {
if sc, ok := reader.(syscall.Conn); ok { if sc, ok := reader.(syscall.Conn); ok {
rawConn, err := sc.SyscallConn() rawConn, err := sc.SyscallConn()
if err != nil { if err != nil {
+15 -3
View File
@@ -5,6 +5,7 @@ package buf
import ( import (
"io" "io"
"sync/atomic"
"syscall" "syscall"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
@@ -143,13 +144,24 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
return mb, nil return mb, nil
} }
var useReadv bool var useReadv atomic.Bool
func init() { func useReadV() bool {
return useReadv.Load()
}
func reloadEnvSettings() error {
const defaultFlagValue = "NOT_DEFINED_AT_ALL" const defaultFlagValue = "NOT_DEFINED_AT_ALL"
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue }) value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
enabled := false
switch value { switch value {
case defaultFlagValue, "auto", "enable": case defaultFlagValue, "auto", "enable":
useReadv = true enabled = true
} }
useReadv.Store(enabled)
return nil
}
func init() {
platform.RegisterEnvReload(reloadEnvSettings)
} }
+3 -1
View File
@@ -10,7 +10,9 @@ import (
"github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/features/stats"
) )
const useReadv = false func useReadV() bool {
return false
}
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader { func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
panic("not implemented") panic("not implemented")
+40
View File
@@ -0,0 +1,40 @@
package platform
import (
"errors"
"sync"
)
var envReloadRegistry = struct {
sync.RWMutex
handlers []func() error
}{}
// RegisterEnvReload registers an environment reload handler and runs it once
// immediately so package defaults keep the same behavior as init-time reads.
func RegisterEnvReload(handler func() error) {
if handler == nil {
return
}
envReloadRegistry.Lock()
envReloadRegistry.handlers = append(envReloadRegistry.handlers, handler)
envReloadRegistry.Unlock()
if err := handler(); err != nil {
panic(err)
}
}
// ReloadEnvSettings refreshes all registered environment-backed package state.
func ReloadEnvSettings() error {
envReloadRegistry.RLock()
handlers := append([]func() error{}, envReloadRegistry.handlers...)
envReloadRegistry.RUnlock()
var errs []error
for _, handler := range handlers {
if err := handler(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
+33 -19
View File
@@ -8,7 +8,7 @@ import (
"io" "io"
"strconv" "strconv"
"strings" "strings"
"time" "sync/atomic"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -27,25 +27,39 @@ var AddrParser = protocol.NewAddressParser(
) )
var ( var (
Show bool Show atomic.Bool
BaseKey []byte baseKey atomic.Value
) )
func init() { func reloadEnvSettings() error {
if strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true" { Show.Store(strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true")
Show = true raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" })
if raw == "" {
ensureBaseKey()
return nil
} }
BaseKey = make([]byte, 32) key, _ := base64.RawURLEncoding.DecodeString(raw)
rand.Read(BaseKey) if len(key) != 32 {
go func() { return errors.New(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(key)))
time.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV }
if raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" }); raw != "" { baseKey.Store(append([]byte(nil), key...))
if BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 { return nil
return }
}
panic(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(BaseKey))) func ensureBaseKey() []byte {
} if key := baseKey.Load(); key != nil {
}() return key.([]byte)
}
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
panic(err)
}
baseKey.Store(key)
return key
}
func init() {
platform.RegisterEnvReload(reloadEnvSettings)
} }
func GetGlobalID(ctx context.Context) (globalID [8]byte) { func GetGlobalID(ctx context.Context) (globalID [8]byte) {
@@ -54,10 +68,10 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
} }
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP && if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") { (inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
h := blake3.New(8, BaseKey) h := blake3.New(8, ensureBaseKey())
h.Write([]byte(inbound.Source.String())) h.Write([]byte(inbound.Source.String()))
copy(globalID[:], h.Sum(nil)) copy(globalID[:], h.Sum(nil))
if Show { if Show.Load() {
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID)) errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
} }
} }
+3
View File
@@ -187,6 +187,9 @@ func NewWithContext(ctx context.Context, config *Config) (*Instance, error) {
} }
func initInstanceWithConfig(config *Config, server *Instance) (bool, error) { func initInstanceWithConfig(config *Config, server *Instance) (bool, error) {
if err := platform.ReloadEnvSettings(); err != nil {
return true, errors.New("failed to reload environment settings").Base(err)
}
server.ctx = context.WithValue(server.ctx, "cone", server.ctx = context.WithValue(server.ctx, "cone",
platform.NewEnvFlag(platform.UseCone).GetValue(func() string { return "" }) != "true") platform.NewEnvFlag(platform.UseCone).GetValue(func() string { return "" }) != "true")
+18 -8
View File
@@ -3,6 +3,7 @@ package policy
import ( import (
"context" "context"
"runtime" "runtime"
"sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
@@ -82,32 +83,41 @@ func ManagerType() interface{} {
return (*Manager)(nil) return (*Manager)(nil)
} }
var defaultBufferSize int32 var defaultBufferSize atomic.Int32
func init() { func reloadEnvSettings() error {
defaultBufferSize.Store(readDefaultBufferSize())
return nil
}
func readDefaultBufferSize() int32 {
const defaultValue = -17 const defaultValue = -17
size := platform.NewEnvFlag(platform.BufferSize).GetValueAsInt(defaultValue) size := platform.NewEnvFlag(platform.BufferSize).GetValueAsInt(defaultValue)
switch size { switch size {
case 0: case 0:
defaultBufferSize = -1 // For pipe to use unlimited size return -1 // For pipe to use unlimited size
case defaultValue: // Env flag not defined. Use default values per CPU-arch. case defaultValue: // Env flag not defined. Use default values per CPU-arch.
switch runtime.GOARCH { switch runtime.GOARCH {
case "arm", "mips", "mipsle": case "arm", "mips", "mipsle":
defaultBufferSize = 0 return 0
case "arm64", "mips64", "mips64le": case "arm64", "mips64", "mips64le":
defaultBufferSize = 4 * 1024 // 4k cache for low-end devices return 4 * 1024 // 4k cache for low-end devices
default: default:
defaultBufferSize = 512 * 1024 return 512 * 1024
} }
default: default:
defaultBufferSize = int32(size) * 1024 * 1024 return int32(size) * 1024 * 1024
} }
} }
func init() {
platform.RegisterEnvReload(reloadEnvSettings)
}
func defaultBufferPolicy() Buffer { func defaultBufferPolicy() Buffer {
return Buffer{ return Buffer{
PerConnection: defaultBufferSize, PerConnection: defaultBufferSize.Load(),
} }
} }
+59
View File
@@ -0,0 +1,59 @@
package conf
import (
"encoding/json"
"os"
"testing"
)
func TestRootEnvAppliesArbitraryValues(t *testing.T) {
const (
valueKey = "XRAY_TEST_CONFIG_ENV"
emptyKey = "XRAY_TEST_CONFIG_EMPTY"
)
t.Setenv(valueKey, "before")
t.Setenv(emptyKey, "before")
config := new(Config)
if err := json.Unmarshal([]byte(`{
"env": {
"XRAY_TEST_CONFIG_ENV": "configured",
"XRAY_TEST_CONFIG_EMPTY": ""
}
}`), config); err != nil {
t.Fatal(err)
}
if _, err := config.Build(); err != nil {
t.Fatal(err)
}
if got := os.Getenv(valueKey); got != "configured" {
t.Fatalf("env %q = %q, want %q", valueKey, got, "configured")
}
if got := os.Getenv(emptyKey); got != "" {
t.Fatalf("env %q = %q, want empty", emptyKey, got)
}
}
func TestEnvConfigOverride(t *testing.T) {
base := EnvConfig{
"ONE": "one",
"TWO": "old",
}
override := EnvConfig{
"TWO": "new",
"THREE": "three",
}
base.Override(override)
want := map[string]string{
"ONE": "one",
"TWO": "new",
"THREE": "three",
}
for key, value := range want {
if got := base[key]; got != value {
t.Fatalf("env %q = %q, want %q", key, got, value)
}
}
}
+22
View File
@@ -3,6 +3,7 @@ package conf
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -379,11 +380,20 @@ func (c *StatsConfig) Build() (*stats.Config, error) {
return &stats.Config{}, nil return &stats.Config{}, nil
} }
type EnvConfig map[string]string
func (c EnvConfig) Override(o EnvConfig) {
for key, value := range o {
c[key] = value
}
}
type Config struct { type Config struct {
// Deprecated: Global transport config is no longer used // Deprecated: Global transport config is no longer used
// left for returning error // left for returning error
Transport map[string]json.RawMessage `json:"transport"` Transport map[string]json.RawMessage `json:"transport"`
Env EnvConfig `json:"env"`
LogConfig *LogConfig `json:"log"` LogConfig *LogConfig `json:"log"`
RouterConfig *RouterConfig `json:"routing"` RouterConfig *RouterConfig `json:"routing"`
DNSConfig *DNSConfig `json:"dns"` DNSConfig *DNSConfig `json:"dns"`
@@ -439,6 +449,12 @@ func (c *Config) Override(o *Config, fn string) {
if o.Transport != nil { if o.Transport != nil {
c.Transport = o.Transport c.Transport = o.Transport
} }
if o.Env != nil {
if c.Env == nil {
c.Env = EnvConfig{}
}
c.Env.Override(o.Env)
}
if o.Policy != nil { if o.Policy != nil {
c.Policy = o.Policy c.Policy = o.Policy
} }
@@ -514,6 +530,12 @@ func (c *Config) Override(o *Config, fn string) {
// Build implements Buildable. // Build implements Buildable.
func (c *Config) Build() (*core.Config, error) { func (c *Config) Build() (*core.Config, error) {
for key, value := range c.Env {
if err := os.Setenv(key, value); err != nil {
return nil, errors.New("failed to apply environment configuration").Base(err)
}
}
if err := PostProcessConfigureFile(c); err != nil { if err := PostProcessConfigureFile(c); err != nil {
return nil, errors.New("failed to post-process configuration file").Base(err) return nil, errors.New("failed to post-process configuration file").Base(err)
} }
+4
View File
@@ -36,6 +36,10 @@ The -confdir=dir flag sets a dir with multiple json config
The -format=json flag sets the format of config files. The -format=json flag sets the format of config files.
Default "auto". Default "auto".
The config root env object sets process environment variables after all config
files are parsed. Variables needed to locate or parse config files must be set
in the process environment before Xray starts.
The -test flag tells Xray to test config files only, The -test flag tells Xray to test config files only,
without launching the server. without launching the server.
+16 -8
View File
@@ -5,6 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"io" "io"
"strings" "strings"
"sync/atomic"
"time" "time"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
@@ -31,12 +32,24 @@ import (
) )
var ( var (
useSplice bool useSplice atomic.Bool
allNetworks [8]bool allNetworks [8]bool
defaultBlockPrivateRule *FinalRule defaultBlockPrivateRule *FinalRule
defaultBlockAllRule *FinalRule defaultBlockAllRule *FinalRule
) )
func reloadEnvSettings() error {
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
enabled := false
switch value {
case defaultFlagValue, "auto", "enable":
enabled = true
}
useSplice.Store(enabled)
return nil
}
func init() { func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler) h := new(Handler)
@@ -48,12 +61,7 @@ func init() {
return h, nil return h, nil
})) }))
const defaultFlagValue = "NOT_DEFINED_AT_ALL" platform.RegisterEnvReload(reloadEnvSettings)
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
switch value {
case defaultFlagValue, "auto", "enable":
useSplice = true
}
for i := range allNetworks { for i := range allNetworks {
allNetworks[i] = true allNetworks[i] = true
@@ -422,7 +430,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
responseDone := func() error { responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
if destination.Network == net.Network_TCP && useSplice && proxy.IsRAWTransportWithoutSecurity(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic if destination.Network == net.Network_TCP && useSplice.Load() && proxy.IsRAWTransportWithoutSecurity(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic
var writeConn net.Conn var writeConn net.Conn
var inTimer *signal.ActivityTimer var inTimer *signal.ActivityTimer
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
+11 -8
View File
@@ -5,6 +5,7 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"hash/crc64" "hash/crc64"
"sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -218,10 +219,17 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil return nil
} }
var enablePadding = false var enablePadding atomic.Bool
func shouldEnablePadding(s protocol.SecurityType) bool { func shouldEnablePadding(s protocol.SecurityType) bool {
return enablePadding || s == protocol.SecurityType_AES128_GCM || s == protocol.SecurityType_CHACHA20_POLY1305 || s == protocol.SecurityType_AUTO return enablePadding.Load() || s == protocol.SecurityType_AES128_GCM || s == protocol.SecurityType_CHACHA20_POLY1305 || s == protocol.SecurityType_AUTO
}
func reloadEnvSettings() error {
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
paddingValue := platform.NewEnvFlag(platform.UseVmessPadding).GetValue(func() string { return defaultFlagValue })
enablePadding.Store(paddingValue != defaultFlagValue)
return nil
} }
func init() { func init() {
@@ -229,10 +237,5 @@ func init() {
return New(ctx, config.(*Config)) return New(ctx, config.(*Config))
})) }))
const defaultFlagValue = "NOT_DEFINED_AT_ALL" platform.RegisterEnvReload(reloadEnvSettings)
paddingValue := platform.NewEnvFlag(platform.UseVmessPadding).GetValue(func() string { return defaultFlagValue })
if paddingValue != defaultFlagValue {
enablePadding = true
}
} }
+14 -4
View File
@@ -27,9 +27,10 @@ type task struct {
} }
var ( var (
conns chan *websocket.Conn conns chan *websocket.Conn
server *http.Server server *http.Server
mu sync.Mutex currentAddr string
mu sync.Mutex
) )
var upgrader = &websocket.Upgrader{ var upgrader = &websocket.Upgrader{
@@ -47,8 +48,13 @@ func Reload() {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
if addr == currentAddr && (addr == "" || server != nil) {
return
}
if server != nil { if server != nil {
server.Close() server.Close()
server = nil
} }
if HasBrowserDialer() { if HasBrowserDialer() {
for len(conns) > 0 { for len(conns) > 0 {
@@ -60,6 +66,7 @@ func Reload() {
} }
conns = nil conns = nil
} }
currentAddr = addr
if addr != "" { if addr != "" {
token := uuid.New() token := uuid.New()
csrfToken := token.String() csrfToken := token.String()
@@ -220,5 +227,8 @@ func CheckOK(conn *websocket.Conn) error {
} }
func init() { func init() {
Reload() platform.RegisterEnvReload(func() error {
Reload()
return nil
})
} }