Release 1.9.0: add VLESS, VMess and Trojan via Xray-core.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package xray
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"vpnclient/internal/config"
|
||||
)
|
||||
|
||||
// Engine runs official XTLS/Xray-core for VLESS / VMess / Trojan.
|
||||
type Engine struct {
|
||||
mu sync.Mutex
|
||||
cmd *exec.Cmd
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
workdir string
|
||||
profile config.Profile
|
||||
stderr io.Writer
|
||||
running bool
|
||||
proto config.Protocol
|
||||
}
|
||||
|
||||
func New(stderr io.Writer) *Engine {
|
||||
if stderr == nil {
|
||||
stderr = os.Stderr
|
||||
}
|
||||
return &Engine{stderr: stderr}
|
||||
}
|
||||
|
||||
func (e *Engine) Protocol() config.Protocol {
|
||||
if e.proto != "" {
|
||||
return e.proto
|
||||
}
|
||||
return config.ProtocolVLESS
|
||||
}
|
||||
|
||||
func (e *Engine) Start(ctx context.Context, profile config.Profile, binDir string) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if e.running {
|
||||
return fmt.Errorf("xray: already running")
|
||||
}
|
||||
|
||||
link, err := Parse(profile.Proxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.proto = link.Protocol
|
||||
|
||||
bin, err := ResolveBinary(binDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workdir, err := os.MkdirTemp("", "vpnclient-xray-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("xray: temp dir: %w", err)
|
||||
}
|
||||
cfgPath := filepath.Join(workdir, "config.json")
|
||||
if err := writeRuntimeConfig(cfgPath, profile); err != nil {
|
||||
os.RemoveAll(workdir)
|
||||
return err
|
||||
}
|
||||
|
||||
runCtx, cancel := context.WithCancel(context.Background())
|
||||
cmd := exec.CommandContext(runCtx, bin, "run", "-c", cfgPath)
|
||||
cmd.Dir = workdir
|
||||
cmd.Stdout = e.stderr
|
||||
cmd.Stderr = e.stderr
|
||||
applySysProcAttr(cmd)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
os.RemoveAll(workdir)
|
||||
return fmt.Errorf("xray: start %s: %w", bin, err)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
e.cmd = cmd
|
||||
e.cancel = cancel
|
||||
e.done = done
|
||||
e.workdir = workdir
|
||||
e.profile = profile
|
||||
e.running = true
|
||||
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
fmt.Fprintf(e.stderr, "xray: process ended: %v\n", err)
|
||||
}
|
||||
e.mu.Lock()
|
||||
e.cleanupLocked()
|
||||
e.mu.Unlock()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
e.mu.Unlock()
|
||||
timer := time.NewTimer(900 * time.Millisecond)
|
||||
defer timer.Stop()
|
||||
var startErr error
|
||||
select {
|
||||
case <-done:
|
||||
startErr = fmt.Errorf("xray: process exited immediately; check link and install-core (xray)")
|
||||
case <-ctx.Done():
|
||||
_ = e.Stop()
|
||||
startErr = ctx.Err()
|
||||
case <-timer.C:
|
||||
e.mu.Lock()
|
||||
alive := e.running
|
||||
e.mu.Unlock()
|
||||
if !alive {
|
||||
startErr = fmt.Errorf("xray: process exited during startup")
|
||||
}
|
||||
}
|
||||
e.mu.Lock()
|
||||
return startErr
|
||||
}
|
||||
|
||||
func (e *Engine) Stop() error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.stopLocked()
|
||||
}
|
||||
|
||||
func (e *Engine) stopLocked() error {
|
||||
if !e.running || e.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
done := e.done
|
||||
if e.cancel != nil {
|
||||
e.cancel()
|
||||
}
|
||||
proc := e.cmd.Process
|
||||
e.mu.Unlock()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
if proc != nil {
|
||||
_ = proc.Kill()
|
||||
}
|
||||
<-done
|
||||
}
|
||||
e.mu.Lock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) cleanupLocked() {
|
||||
if e.workdir != "" {
|
||||
_ = os.RemoveAll(e.workdir)
|
||||
e.workdir = ""
|
||||
}
|
||||
e.cmd = nil
|
||||
e.cancel = nil
|
||||
e.running = false
|
||||
}
|
||||
|
||||
func (e *Engine) Running() bool {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.running
|
||||
}
|
||||
|
||||
func (e *Engine) LocalHTTPProxy() (string, bool) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if !e.running {
|
||||
return "", false
|
||||
}
|
||||
return e.profile.HTTPListenHostPort()
|
||||
}
|
||||
|
||||
func (e *Engine) LocalSOCKSProxy() (string, bool) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if !e.running {
|
||||
return "", false
|
||||
}
|
||||
return e.profile.SOCKSListenHostPort()
|
||||
}
|
||||
Reference in New Issue
Block a user