package libv2ray import ( "fmt" "io" "log" "os" "path/filepath" "strconv" "strings" "sync" coreapplog "github.com/xtls/xray-core/app/log" corecommlog "github.com/xtls/xray-core/common/log" corefilesystem "github.com/xtls/xray-core/common/platform/filesystem" core "github.com/xtls/xray-core/core" corestats "github.com/xtls/xray-core/features/stats" coreserial "github.com/xtls/xray-core/infra/conf/serial" _ "github.com/xtls/xray-core/main/distro/all" browser_dialer "github.com/xtls/xray-core/transport/internet/browser_dialer" mobasset "golang.org/x/mobile/asset" ) // Constants for environment variables const ( coreAsset = "xray.location.asset" coreCert = "xray.location.cert" xudpBaseKey = "xray.xudp.basekey" tunFdKey = "xray.tun.fd" browserDialerAddress = "xray.browser.dialer" libVersion = 40 // Library version, update here only ) // CoreController represents a controller for managing Xray core instance lifecycle type CoreController struct { CallbackHandler CoreCallbackHandler statsManager corestats.Manager coreMutex sync.Mutex coreInstance *core.Instance IsRunning bool } // CoreCallbackHandler defines interface for receiving callbacks and notifications from the core service type CoreCallbackHandler interface { Startup() int Shutdown() int OnEmitStatus(int, string) int } // consoleLogWriter implements a log writer without datetime stamps // as Android system already adds timestamps to each log line type consoleLogWriter struct { logger *log.Logger // Standard logger } // setEnvVariable safely sets an environment variable and logs any errors encountered. func setEnvVariable(key, value string) { if err := os.Setenv(key, value); err != nil { log.Printf("Failed to set environment variable %s: %v. Please check your configuration.", key, err) } } // InitCoreEnv initializes environment variables and file system handlers for the core // It sets up asset path, certificate path, XUDP base key and customizes the file reader // to support Android asset system func InitCoreEnv(envPath string, key string) { // Set asset/cert paths if len(envPath) > 0 { setEnvVariable(coreAsset, envPath) setEnvVariable(coreCert, envPath) } // Set XUDP encryption key if len(key) > 0 { setEnvVariable(xudpBaseKey, key) } // Custom file reader with path validation corefilesystem.NewFileReader = func(path string) (io.ReadCloser, error) { if _, err := os.Stat(path); os.IsNotExist(err) { _, file := filepath.Split(path) return mobasset.Open(file) } return os.Open(path) } } // NewCoreController initializes and returns a new CoreController instance // Sets up the console log handler and associates it with the provided callback handler func NewCoreController(s CoreCallbackHandler) *CoreController { // Register custom logger if err := coreapplog.RegisterHandlerCreator( coreapplog.LogType_Console, func(lt coreapplog.LogType, options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) { return corecommlog.NewLogger(createStdoutLogWriter()), nil }, ); err != nil { log.Printf("Failed to register log handler: %v", err) } return &CoreController{ CallbackHandler: s, } } // StartLoop initializes and starts the core processing loop // Thread-safe method that configures and runs the Xray core with the provided configuration // Returns immediately if the core is already running func (x *CoreController) StartLoop(configContent string, tunFd int32) (err error) { // Set TUN fd key, 0 means do not use TUN setEnvVariable(tunFdKey, strconv.Itoa(int(tunFd))) x.coreMutex.Lock() defer x.coreMutex.Unlock() if x.IsRunning { log.Println("Core is already running") return nil } return x.doStartLoop(configContent) } // StopLoop safely stops the core processing loop and releases resources // Thread-safe method that shuts down the core instance and triggers necessary callbacks func (x *CoreController) StopLoop() error { x.coreMutex.Lock() defer x.coreMutex.Unlock() if x.IsRunning { x.doShutdown() x.CallbackHandler.Shutdown() x.CallbackHandler.OnEmitStatus(0, "Core stopped") } return nil } // CheckVersionX returns the library and Xray versions func CheckVersionX() string { return fmt.Sprintf("Lib v%d, Xray-core v%s", libVersion, core.Version()) } // ReconcileBrowserDialer updates the browser dialer address and reloads its configuration // If the dialer address is empty, it will disable the browser dialer and close existing connections func ReconcileBrowserDialer(dialerAddr string) { setEnvVariable(browserDialerAddress, dialerAddr) browser_dialer.Reload() } // doShutdown shuts down the Xray instance and cleans up resources func (x *CoreController) doShutdown() { if x.coreInstance != nil { if err := x.coreInstance.Close(); err != nil { log.Printf("core shutdown error: %v", err) } x.coreInstance = nil } x.IsRunning = false x.statsManager = nil } // doStartLoop sets up and starts the Xray core func (x *CoreController) doStartLoop(configContent string) error { log.Println("initializing core...") config, err := coreserial.LoadJSONConfig(strings.NewReader(configContent)) if err != nil { return fmt.Errorf("config error: %w", err) } x.coreInstance, err = core.New(config) if err != nil { return fmt.Errorf("core init failed: %w", err) } x.statsManager = x.coreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager) log.Println("starting core...") x.IsRunning = true if err := x.coreInstance.Start(); err != nil { x.IsRunning = false return fmt.Errorf("startup failed: %w", err) } x.CallbackHandler.Startup() x.CallbackHandler.OnEmitStatus(0, "Started successfully, running") log.Println("Starting core successfully") return nil } // Log writer implementation func (w *consoleLogWriter) Write(s string) error { w.logger.Print(s) return nil } func (w *consoleLogWriter) Close() error { return nil } // createStdoutLogWriter creates a logger that won't print date/time stamps func createStdoutLogWriter() corecommlog.WriterCreator { return func() corecommlog.Writer { return &consoleLogWriter{ logger: log.New(os.Stdout, "", 0), } } }