2020-11-26 13:35:52 +08:00
|
|
|
package libv2ray
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
coreapplog "github.com/xtls/xray-core/app/log"
|
|
|
|
corecommlog "github.com/xtls/xray-core/common/log"
|
|
|
|
corenet "github.com/xtls/xray-core/common/net"
|
|
|
|
corefilesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
2024-12-29 01:11:08 -05:00
|
|
|
"github.com/xtls/xray-core/common/serial"
|
2025-04-17 15:52:23 +08:00
|
|
|
core "github.com/xtls/xray-core/core"
|
|
|
|
corestats "github.com/xtls/xray-core/features/stats"
|
|
|
|
coreserial "github.com/xtls/xray-core/infra/conf/serial"
|
2020-12-04 11:10:47 +08:00
|
|
|
_ "github.com/xtls/xray-core/main/distro/all"
|
2025-04-17 15:52:23 +08:00
|
|
|
mobasset "golang.org/x/mobile/asset"
|
2020-11-26 13:35:52 +08:00
|
|
|
)
|
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
// Constants for environment variables
|
2020-11-26 13:35:52 +08:00
|
|
|
const (
|
2025-04-17 15:52:23 +08:00
|
|
|
coreAsset = "xray.location.asset"
|
|
|
|
coreCert = "xray.location.cert"
|
2023-12-03 14:28:36 -05:00
|
|
|
xudpBaseKey = "xray.xudp.basekey"
|
2020-11-26 13:35:52 +08:00
|
|
|
)
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// CoreController represents a controller for managing Xray core instance lifecycle
|
|
|
|
type CoreController struct {
|
|
|
|
CallbackHandler CoreCallbackHandler
|
|
|
|
statsManager corestats.Manager
|
|
|
|
coreMutex sync.Mutex
|
2025-04-18 17:14:49 +08:00
|
|
|
coreInstance *core.Instance
|
2025-04-17 15:52:23 +08:00
|
|
|
IsRunning bool
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// CoreCallbackHandler defines interface for receiving callbacks and notifications from the core service
|
|
|
|
type CoreCallbackHandler interface {
|
|
|
|
Startup() int
|
2020-11-26 13:35:52 +08:00
|
|
|
Shutdown() int
|
|
|
|
OnEmitStatus(int, string) int
|
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// consoleLogWriter implements a log writer without datetime stamps
|
|
|
|
// as Android system already adds timestamps to each log line
|
|
|
|
type consoleLogWriter struct {
|
2025-04-29 03:08:47 +03:30
|
|
|
logger *log.Logger // Standard logger
|
2025-04-17 15:52:23 +08:00
|
|
|
}
|
2020-11-26 13:35:52 +08:00
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// 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) {
|
2025-04-29 03:08:47 +03:30
|
|
|
// Set asset/cert paths
|
2025-04-17 15:52:23 +08:00
|
|
|
if len(envPath) > 0 {
|
2025-04-29 03:08:47 +03:30
|
|
|
if err := os.Setenv(coreAsset, envPath); err != nil {
|
2025-04-29 10:03:09 +08:00
|
|
|
log.Printf("Failed to set %s: %v", coreAsset, err)
|
2025-04-29 03:08:47 +03:30
|
|
|
}
|
|
|
|
if err := os.Setenv(coreCert, envPath); err != nil {
|
2025-04-29 10:03:09 +08:00
|
|
|
log.Printf("Failed to set %s: %v", coreCert, err)
|
2025-04-29 03:08:47 +03:30
|
|
|
}
|
2025-03-27 14:54:48 +03:30
|
|
|
}
|
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
// Set XUDP encryption key
|
|
|
|
if len(key) > 0 {
|
|
|
|
if err := os.Setenv(xudpBaseKey, key); err != nil {
|
2025-04-29 10:03:09 +08:00
|
|
|
log.Printf("Failed to set %s: %v", xudpBaseKey, err)
|
2025-04-29 03:08:47 +03:30
|
|
|
}
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
2025-03-27 14:54:48 +03:30
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
// Custom file reader with path validation
|
2025-04-17 15:52:23 +08:00
|
|
|
corefilesystem.NewFileReader = func(path string) (io.ReadCloser, error) {
|
2025-05-03 04:10:25 +03:30
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
|
|
_, file := filepath.Split(path)
|
|
|
|
return mobasset.Open(file)
|
2025-04-17 15:52:23 +08:00
|
|
|
}
|
2025-05-03 04:10:25 +03:30
|
|
|
return os.Open(path)
|
2025-03-27 14:54:48 +03:30
|
|
|
}
|
2025-04-17 15:52:23 +08:00
|
|
|
}
|
2025-03-27 14:54:48 +03:30
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// 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 {
|
2025-04-29 03:08:47 +03:30
|
|
|
// Register custom logger
|
|
|
|
if err := coreapplog.RegisterHandlerCreator(
|
|
|
|
coreapplog.LogType_Console,
|
|
|
|
func(lt coreapplog.LogType, options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) {
|
2025-04-17 15:52:23 +08:00
|
|
|
return corecommlog.NewLogger(createStdoutLogWriter()), nil
|
2025-04-29 03:08:47 +03:30
|
|
|
},
|
|
|
|
); err != nil {
|
2025-04-29 10:03:09 +08:00
|
|
|
log.Printf("Failed to register log handler: %v", err)
|
2025-04-29 03:08:47 +03:30
|
|
|
}
|
2025-04-17 15:52:23 +08:00
|
|
|
|
|
|
|
return &CoreController{
|
|
|
|
CallbackHandler: s,
|
|
|
|
}
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// 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) (err error) {
|
|
|
|
x.coreMutex.Lock()
|
|
|
|
defer x.coreMutex.Unlock()
|
|
|
|
|
|
|
|
if x.IsRunning {
|
2025-04-29 10:03:09 +08:00
|
|
|
log.Println("Core is already running")
|
2025-04-17 15:52:23 +08:00
|
|
|
return nil
|
2025-03-27 14:54:48 +03:30
|
|
|
}
|
2025-04-17 15:52:23 +08:00
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
return x.doStartLoop(configContent)
|
2025-03-27 14:54:48 +03:30
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// 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()
|
2025-03-28 05:23:10 +03:30
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
if x.IsRunning {
|
|
|
|
x.doShutdown()
|
2025-04-29 10:03:09 +08:00
|
|
|
x.CallbackHandler.OnEmitStatus(0, "Core stopped")
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
2025-03-28 05:23:10 +03:30
|
|
|
return nil
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// QueryStats retrieves and resets traffic statistics for a specific outbound tag and direction
|
|
|
|
// Returns the accumulated traffic value and resets the counter to zero
|
|
|
|
// Returns 0 if the stats manager is not initialized or the counter doesn't exist
|
|
|
|
func (x *CoreController) QueryStats(tag string, direct string) int64 {
|
|
|
|
if x.statsManager == nil {
|
2020-11-26 13:35:52 +08:00
|
|
|
return 0
|
|
|
|
}
|
2025-04-17 15:52:23 +08:00
|
|
|
counter := x.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
|
2020-11-26 13:35:52 +08:00
|
|
|
if counter == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return counter.Set(0)
|
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// MeasureDelay measures network latency to a specified URL through the current core instance
|
|
|
|
// Uses a 12-second timeout context and returns the round-trip time in milliseconds
|
|
|
|
// An error is returned if the connection fails or returns an unexpected status
|
|
|
|
func (x *CoreController) MeasureDelay(url string) (int64, error) {
|
2020-11-26 13:35:52 +08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
2025-03-28 05:23:10 +03:30
|
|
|
defer cancel()
|
2020-11-26 13:35:52 +08:00
|
|
|
|
2025-04-18 17:14:49 +08:00
|
|
|
return measureInstDelay(ctx, x.coreInstance, url)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-03-28 05:23:10 +03:30
|
|
|
// MeasureOutboundDelay measures the outbound delay for a given configuration and URL
|
2024-05-12 11:15:04 +08:00
|
|
|
func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) {
|
2025-04-17 15:52:23 +08:00
|
|
|
config, err := coreserial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
2020-11-26 13:35:52 +08:00
|
|
|
if err != nil {
|
2025-04-29 03:08:47 +03:30
|
|
|
return -1, fmt.Errorf("config load error: %w", err)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
// Simplify config for testing
|
2020-11-26 13:35:52 +08:00
|
|
|
config.Inbound = nil
|
2024-12-29 01:11:08 -05:00
|
|
|
var essentialApp []*serial.TypedMessage
|
|
|
|
for _, app := range config.App {
|
2025-04-29 03:08:47 +03:30
|
|
|
if app.Type == "xray.app.proxyman.OutboundConfig" ||
|
|
|
|
app.Type == "xray.app.dispatcher.Config" ||
|
|
|
|
app.Type == "xray.app.log.Config" {
|
2024-12-29 01:11:08 -05:00
|
|
|
essentialApp = append(essentialApp, app)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
config.App = essentialApp
|
2020-11-26 13:35:52 +08:00
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
inst, err := core.New(config)
|
2020-11-26 13:35:52 +08:00
|
|
|
if err != nil {
|
2025-04-29 03:08:47 +03:30
|
|
|
return -1, fmt.Errorf("instance creation failed: %w", err)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
if err := inst.Start(); err != nil {
|
|
|
|
return -1, fmt.Errorf("startup failed: %w", err)
|
|
|
|
}
|
2025-03-28 05:23:10 +03:30
|
|
|
defer inst.Close()
|
|
|
|
return measureInstDelay(context.Background(), inst, url)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// CheckVersionX returns the library and Xray versions
|
|
|
|
func CheckVersionX() string {
|
|
|
|
var version = 31
|
|
|
|
return fmt.Sprintf("Lib v%d, Xray-core v%s", version, core.Version())
|
|
|
|
}
|
2020-11-26 13:35:52 +08:00
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// doShutdown shuts down the Xray instance and cleans up resources
|
|
|
|
func (x *CoreController) doShutdown() {
|
2025-04-18 17:14:49 +08:00
|
|
|
if x.coreInstance != nil {
|
2025-04-29 03:08:47 +03:30
|
|
|
if err := x.coreInstance.Close(); err != nil {
|
|
|
|
log.Printf("core shutdown error: %v", err)
|
|
|
|
}
|
2025-04-18 17:14:49 +08:00
|
|
|
x.coreInstance = nil
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
2025-04-17 15:52:23 +08:00
|
|
|
x.IsRunning = false
|
|
|
|
x.statsManager = nil
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-04-17 15:52:23 +08:00
|
|
|
// doStartLoop sets up and starts the Xray core
|
|
|
|
func (x *CoreController) doStartLoop(configContent string) error {
|
2025-04-29 03:08:47 +03:30
|
|
|
log.Println("initializing core...")
|
2025-04-17 15:52:23 +08:00
|
|
|
config, err := coreserial.LoadJSONConfig(strings.NewReader(configContent))
|
|
|
|
if err != nil {
|
2025-04-29 03:08:47 +03:30
|
|
|
return fmt.Errorf("config error: %w", err)
|
2025-04-17 15:52:23 +08:00
|
|
|
}
|
|
|
|
|
2025-04-18 17:14:49 +08:00
|
|
|
x.coreInstance, err = core.New(config)
|
2025-04-17 15:52:23 +08:00
|
|
|
if err != nil {
|
2025-04-29 03:08:47 +03:30
|
|
|
return fmt.Errorf("core init failed: %w", err)
|
2025-04-17 15:52:23 +08:00
|
|
|
}
|
2025-04-18 17:14:49 +08:00
|
|
|
x.statsManager = x.coreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager)
|
2025-04-17 15:52:23 +08:00
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
log.Println("starting core...")
|
2025-04-17 15:52:23 +08:00
|
|
|
x.IsRunning = true
|
2025-04-18 17:14:49 +08:00
|
|
|
if err := x.coreInstance.Start(); err != nil {
|
2025-04-17 15:52:23 +08:00
|
|
|
x.IsRunning = false
|
2025-04-29 03:08:47 +03:30
|
|
|
return fmt.Errorf("startup failed: %w", err)
|
2025-04-17 15:52:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
x.CallbackHandler.Startup()
|
2025-04-29 10:03:09 +08:00
|
|
|
x.CallbackHandler.OnEmitStatus(0, "Started successfully, running")
|
2025-04-17 15:52:23 +08:00
|
|
|
|
2025-04-29 10:03:09 +08:00
|
|
|
log.Println("Starting core successfully")
|
2025-04-17 15:52:23 +08:00
|
|
|
return nil
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
2025-03-28 05:23:10 +03:30
|
|
|
// measureInstDelay measures the delay for an instance to a given URL
|
2025-04-17 15:52:23 +08:00
|
|
|
func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int64, error) {
|
2020-11-26 13:35:52 +08:00
|
|
|
if inst == nil {
|
2025-04-29 03:08:47 +03:30
|
|
|
return -1, errors.New("nil instance")
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tr := &http.Transport{
|
|
|
|
TLSHandshakeTimeout: 6 * time.Second,
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
2025-04-17 15:52:23 +08:00
|
|
|
dest, err := corenet.ParseDestination(fmt.Sprintf("%s:%s", network, addr))
|
2020-11-26 13:35:52 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2025-04-17 15:52:23 +08:00
|
|
|
return core.Dial(ctx, inst, dest)
|
2020-11-26 13:35:52 +08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2025-03-28 05:23:10 +03:30
|
|
|
client := &http.Client{
|
2020-11-26 13:35:52 +08:00
|
|
|
Transport: tr,
|
|
|
|
Timeout: 12 * time.Second,
|
|
|
|
}
|
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
if url == "" {
|
2024-05-12 11:15:04 +08:00
|
|
|
url = "https://www.google.com/generate_204"
|
|
|
|
}
|
2025-04-29 03:08:47 +03:30
|
|
|
|
2024-05-12 11:15:04 +08:00
|
|
|
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
|
2020-11-26 13:35:52 +08:00
|
|
|
start := time.Now()
|
2025-03-28 05:23:10 +03:30
|
|
|
resp, err := client.Do(req)
|
2020-11-26 13:35:52 +08:00
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
2025-03-28 05:23:10 +03:30
|
|
|
defer resp.Body.Close()
|
2024-05-12 11:15:04 +08:00
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
2025-04-29 03:08:47 +03:30
|
|
|
return -1, fmt.Errorf("invalid status: %s", resp.Status)
|
2020-11-26 13:35:52 +08:00
|
|
|
}
|
|
|
|
return time.Since(start).Milliseconds(), nil
|
|
|
|
}
|
2022-06-26 20:18:07 +08:00
|
|
|
|
2025-04-29 03:08:47 +03:30
|
|
|
// Log writer implementation
|
2022-06-26 20:18:07 +08:00
|
|
|
func (w *consoleLogWriter) Write(s string) error {
|
|
|
|
w.logger.Print(s)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *consoleLogWriter) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-03-28 05:23:10 +03:30
|
|
|
// createStdoutLogWriter creates a logger that won't print date/time stamps
|
2025-04-17 15:52:23 +08:00
|
|
|
func createStdoutLogWriter() corecommlog.WriterCreator {
|
|
|
|
return func() corecommlog.Writer {
|
2022-06-26 20:18:07 +08:00
|
|
|
return &consoleLogWriter{
|
2025-03-28 05:23:10 +03:30
|
|
|
logger: log.New(os.Stdout, "", 0),
|
|
|
|
}
|
2022-06-26 20:18:07 +08:00
|
|
|
}
|
2024-05-12 11:15:04 +08:00
|
|
|
}
|