Files

208 lines
6.1 KiB
Go

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),
}
}
}